4 ms·
> I would have thought the lazy semantics of Haskel would trip you up here. Laziness doesn't come into it, since you won't even manage to compile your code, le
by chriswarbo 12y ago
> I would have thought the lazy semantics of Haskel would trip you up here.
Laziness doesn't come into it, since you won't even manage to compile your code, let alone run it, if you try to use a "Maybe FileHandle" when a "FileHandle" is expected.
With that said, laziness does cause problems related to file handles, but those have nothing to do with unhandled failures. The problem is that files are a limited resource which we should release as soon as possible (just like memory, sockets, etc.). Lazy evaluation is at odds with this, since it essentially defers function calls to the last possible moment. One way of thinking about it is that laziness is "call-by-need", but we never really need the return values of functions like "closeFile".
- taeric 12y agoRight, I would have thought you could get tripped up by getting a FileHandle that is invalid, but you wouldn't know it till you get to the point that you use it. Though, I am clearly not familiar with IO API design in Haskel. :)
- dllthomas 12y agoIf something returns Maybe FileHandle, and it can't provide you a valid FileHandle, it'll provide Nothing (and if it can provide a valid FileHandle, it will provide "Just" that filehandle). Edited to add: Note that this can be the case in Haskell when you're relying on exceptions for error handling. That's the opposite approach from option types, though.
- taeric 12y agoIn my case, I thought the laziness could be more crazy, where you created a function that would evaluate to a FileHandle, but until you actually evaluate that function, you don't have a FileHandle. So, depending on just how "lazy" the program truly is, sure you passed it something that unwraps a Maybe, but only once you actually go to use it. That make sense? Consider this fun bit of nonsense scala def foo(x:=>String): Unit = { println("Hello") println(x) } If I call this as "foo(None.get)", then it will print "Hello" before barfing. In fact, if I didn't evaluate x, it wouldn't even barf. I had thought the laziness of Haskel was that ++. (Meaning I thought if I stored x off to another also lazy reference, it wouldn't barf right away.) To make that a little more clear of what I meant, some even more amusing scala. class Foo(x: => String) { def happy = println("Doing good.") def sad = println(x) } If I create something with "new Foo(None.get)", I can call happy on this object all day long. If I ever call sad, I get an exception.
- dllthomas 12y agoThe relevant gotcha is when doing lazy IO in Haskell. The general answer is "don't do that" - there are a number of approaches for how. (Lazy IO is fine and quite convenient for things of roughly the scale of a shell script. It's not a good fit to bigger things.)
- taeric 12y agoI'm not sure I see how this changes much. My point is that with laziness, your explosion doesn't happen right where you unwrap a Maybe in the code. It happens where you evaluate the unwrapping. Apologies for the ninja edit above, but this scala seems to show it isn't just IO that is at danger here. class Foo(x : =>String) { def happy = println("I'm good.") lazy val ack = x def sad = println(ack) } Again, if I do "new Foo(None.get)", I can call happy as much as I want, but "sad" will blow up. Now, I do not and could not claim that this sort of nonsense would have proliferated with the same abandon as null. I am claiming that this is not that unlike a null pointer exception, though.
- dllthomas 12y agoI think the equivalent to None.get is fromJust Nothing. And yes, it's the equivalent of an unchecked nullable. The Haskell community generally frowns on fromJust (along with other unnecessarily partial functions). The better approach is to either pattern match: case maybe_string of Just string -> newFoo string Nothing -> defaultFoo Or to use one of the relevant higher-order functions: fmap newFoo maybe_string :: Maybe Foo maybe defaultFoo newFoo maybe_string :: Foo
- tel 12y agoYeah, exception resolution is pretty weird in Haskell due to laziness. In some sense, you end up thinking of exceptions as being buried in values in the language instead of exposed by computation—this is your null-value landmine problem exactly http://research.microsoft.com/en-us/um/people/simonpj/papers/imprecise-exn.htm http://research.microsoft.com/en-us/um/people/simonpj/papers... This is also why the Control.Exception module exposes `evaluate`, which embeds a value in an IO computation and thus resolves this problem... If you remember to use it. http://hackage.haskell.org/package/base-4.7.0.0/docs/Control-Exception.html#g:8 http://hackage.haskell.org/package/base-4.7.0.0/docs/Control... Fortunately, in idiomatic Haskell exceptions are considered to be an expert feature (basically I see them in concurrent IO or resource management code only) and partial functions—usually introduced by incomplete pattern matching like `None.get` does—are veboten. It's not perfect, but -Wall will help with that.
- scott_s 12y agoRather than being specific to IO, I think you're missing an understanding of algebraic data types, and their use in strongly and statically typed languages. The wikipedia entry is a decent place to start: http://en.wikipedia.org/wiki/Algebraic_data_type http://en.wikipedia.org/wiki/Algebraic_data_type
- taeric 12y agoActually, I think I'm just having more of this conversation in my head than I am in this forum. For better and worse. :) My point here was more that just because you have a file handle doesn't mean you have a valid place to write data. Making it optional would possibly prevent some bugs, true. However, it doesn't really help as soon as you have a filehandle to a full filesystem, for example. (Or, well, any other problem that usually happens to cause grief with the filesystems. You got a file, but by the time you went to use it the system was full and you couldn't, etc.) Regardless, I should have been clearer on many points (and I fully accept I was flat out wrong on at least a few :) ). I do think null pointers are bad. I also know with proper static analysis tools, you don't need a whole new type system to make things better. Unless you are considering tools such as Coverity and friends some form of type system.
- dllthomas 12y ago"Unless you are considering tools such as Coverity and friends some form of type system." I would, for the record. There was a thread some days ago (https://news.ycombinator.com/item?id=7916554 https://news.ycombinator.com/item?id=7916554) where I described some hackary I used to get particular guarantees out of C's type system. My response in https://news.ycombinator.com/item?id=7918423 https://news.ycombinator.com/item?id=7918423 seems tangentially relevant here. Type systems aren't (only/necessarily) features of a language.
- taeric 12y agoThat seems like an odd stance to take, to me. Am I the one that is actually on the "odd" end of this debate?