5 ms·
I don't have much experience with haskell beyond Learn You A Haskell (a book), but I wonder how beneficial the no null pointer errors are in practice. The way
by tieTYT 13y ago
I don't have much experience with haskell beyond Learn You A Haskell (a book), but I wonder how beneficial the no null pointer errors are in practice.
The way haskell handles this reminds me of checked exceptions in Java. In java if you read from a file, your code won't compile unless you have a catch block that handles the possibility of an IO exception. This is called a checked exception because you have to check for the possibility or else your code won't compile.
I know many Java programmers handle checked exceptions by wrapping the checked exception in a unchecked exception so they don't have to deal with it. Don't Haskell developers end up doing the same thing with their Maybe concept?
Haskell avoids null by using a Maybe type/class (I always forget the terminology). A Maybe can evaluate to either a value or a type that represents the absence of a value. (This is an oversimplification for the consideration of people who know nothing about Haskell)
For example, you've got an associative map data type and you find an element in there. At the time of writing the code you "knew" that the element "has to be there". Haskell makes you deal with the possibility that it's not. Won't most developers just end up throwing an exception in that case so they don't have to deal with the impossible possibility? Then, x months from now when the code gets changed so that the map won't have the element there, all the sudden your code gets an error. How is this different from a null pointer exception in any other language?
(Part of me is ignorant and part of me is playing the devil's advocate.)
- nightski 13y agoTwo large differences - a.) Haskell gives you tools to deal with it. Unlike checked exceptions which must be restated and dealt with in every function along the chain - Haskell provides powerful abstraction capabilities to manage this. (You can use maybe values while most of the functions know nothing about them) b.) This is a tool that can help you if you don't shoot yourself in the foot. The compiler can provide guarantees that no one "just throws an exception" by throwing warnings/errors on partial functions and or the use of unsafePerformIO. These are the only two escape hatches that would allow you to do such a thing but they are both heavily frowned upon and detectable by the compiler.
- tikhonj 13y agoThis really depends. The usual rule of thumb is that a runtime exception (at least in pure code) is for bugs in the code and explicit methods like Maybe are for bugs in the input. So if getting a Nothing from your map means your own code is broken, an exception is perfectly fine; if it means your code was called wrong, you want to just return a Maybe. In practice, most of the time, you end up either coming up with a default or returning the Maybe. This is greatly helped by the fact that just propagating Maybe value is really easy because Maybe is a member of a bunch of standard typeclasses like Applicative, Alternative and Monad. Thanks to this, I've found most of my code follows a simple pattern: it pushes the maybe values through until it has a meaningful default. This is safe, simple and fairly elegant. At some point, I either pattern match against it or use a function like fromMaybe, which then allows me to plug everything back into normal code.
- tieTYT 13y agoFrom this description it sounds like Maybe is similar to checked exceptions in ways I didn't even realize when I wrote my comment. The difference is that Haskell's syntax is pleasant and concise to work with. It would be really annoying if Java's Map type threw a checked exception when you called get() because try/catch is so verbose and difficult to refactor around. But if it weren't annoying, it would probably make the API better and save me from shooting myself in the foot.
- Dewie 13y ago> For example, you've got an associative map data type and you find an element in there. At the time of writing the code you "knew" that the element "has to be there". Haskell makes you deal with the possibility that it's not. Won't most developers just end up throwing an exception in that case so they don't have to deal with the impossible possibility? Then, x months from now when the code gets changed so that the map won't have the element there, all the sudden your code gets an error. How is this different from a null pointer exception in any other language? I don't understand. You're saying that there is a mismatch between what the programmer knows and what the compiler can deduce? That you know, from the state the program must be in, that the map has to contain the value associated with the given key? I can't think of an example were that would be the case off the top of my head. I guess a similar case is if you have to perform the head function (take first element) on a list, and you know that the list is non-empty. If you weren't sure, then if you don't check that the list is non-empty before taking head on the list, you might get an error at runtime from taking head on an empty list, which is undefined in Haskell, but in a dependently typed language is impossible to even do at runtime (much like null pointer exceptions are impossible to get in Haskell). Why would you throw an exception in Java? If you're sure that a value will be returned (as opposed to a sentinel value... aka null pointer), then just happily dereference it. You seem to be coming at this from a weird angle. The Maybe type let's you statically mark all things that might be "null", which is a big improvement from the Wild Wild West of everthing might be null. Now you seem to be coming at this from the "how does this make dealing with the semantics of absent values easier?", to which I guess, no, it doesn't. You still have to mull out what you should do if things are missing, or if they deviate from the normal. But you can throw exceptions, define things that you don't want to deal with or that truly are undefined, as simply "undefined", much like in most other languages.
- spullara 13y ago>I don't understand. You're saying that there is a mismatch between what the programmer knows and what the compiler can deduce? That you know, from the state the program must be in, that the map has to contain the value associated with the given key? I can't think of an example were that would be the case off the top of my head. Maybe you just put it there or it wasn't Maybe when you checked it?
- sold 13y agoI think that in the particular case of an associative map, you are generally correct: there is no gain. However, in other situations the Haskell approach is much better. Say you have a Java class with five fields, three of them are nullable (i.e. have a sensible notion of being null) and two are not (have to be non-null unless there is a programming error). There are tons of places where you can mess up: assign null to an invalid variable, assign "x = f()" not noticing f can return null, call "y.f()" forgetting that y can be null etc. However, if your type system handles nullability, you will get a compile-time error on each of them. More: if you change nullability of a field, compiler errors will point out places that have to be changed. By throwing an exception when you "know" an element in the hashmap has to be there, you resign from safety offered by the compiler. In Haskell this is a code smell; Haskellers are less content with such hacks and might try to redesign the structure so that the invariants are enforced by the type system.
- gizmo686 13y agoGennerally when you have null pointer erros, it is because the developer did not realize that there was the possibility of having a null value at that point in code. Adding this information to the type system allows the developer to know exactly where the issue might occur (and have the compiler complain if it is not handled). Also, in terms of use, maybe has another major advantage over null. Many times in languages with null you see the pattern if (foo==null) {return null} else {...} This pattern is handled automaticly by maybe; If you try applying a function to Nothing (maybe's version of null), then the result of that function is also Nothing, even if the function itself was not designed to handle Maybes. Additionally, as a matter of culture, Haskell programmers rarely throw exceptions.
- wereHamster 13y ago> Additionally, as a matter of culture, Haskell programmers rarely throw exceptions. The writers of unix and other base libraries would disagree. See for example `head`. Or most functions that calls into C where the C function fails with an error. In that case Haskell blows up into your face, and you have to wrap the function invocation in a catch.
- Peaker 13y agoOr replace head with a better function.
- mercurial 13y agoSee the fantastic errors module, which has safe versions of head, etc.
- epsylon 13y agoOr use pattern matching.
- mercurial 13y agoPattern matching is great, you can usually use it instead of head, but you want something like Control.Error.Safe.tryRead to replace other unsafe functions like read.
- rspeele 13y agoWorking with a Maybe isn't that much better than working with a type that might be null in Java, C#, or C++. You do have to go slightly farther out of your way to be "bad" and ignore the Nothing case, but not much -- you could wrap all your Maybe values with fromJust and turn them into exceptions as you suggest. The real benefit is the ability to have things that aren't Maybe. I would say that in any language, most functions don't have meaningful answers for null inputs (especially when you count the 'this' object input in object oriented languages). Most functions also don't produce nullable results. Yet, in C# or Java, reference types could always be null. The cases where that nullability is a real possibility that you need to handle are difficult to identify. Having nullability be opt-in with Maybe, rather than an inescapable part of all reference types, lets you leave worrying about handling the null case to the typically smaller amount of times where it can actually meaningfully occur. Hopefully that makes doing the handling correctly more likely.
- runT1ME 13y agoMaybe (option) avoids race conditions in a concurrent environment too. I think you are missing a big part of what Maybe is good for.
- shangaslammi 13y agoThe important bit isn't the Maybe-types, but all types that are _not_ Maybe. If types aren't non-nullable by default then any function parameter or return value can potentially be null and a robust program practically needs to have null checks _everywhere_. Otherwise, when your program throws a NullPointerException you'll have no way to know where that null originated from. In Haskell, the types guarantee that you never ever have to check for null (in fact, you can't even) unless the function's type explicitly allows for the possibility of having a null. In addition, you have several ways to compose function calls that might return null in such a way that you don't have to check for each null case separately.
- laureny 13y ago> Otherwise, when your program throws a NullPointerException you'll have no way to know where that null originated from. Actually, it's the other way around: NullPointerExceptions are typically trivial to track down. Finding out at what part of a monadic computation the return of a function put a None when there should have been a Some is much more tricky.
- tome 13y ago> Finding out at what part of a monadic computation the return of a function put a None when there should have been a Some is much more tricky. The point is you don't have to, because you don't use Maybe where you know statically that there can't be Nothing!
- Peaker 13y agoA typical Haskell project will have thousands upon thousands of uses of Maybe, and only a handful of calls to the unsafe "unjust". Sure, sometime the maybe wrapper is wrong and you have to force it but it is exceedingly rare and in the vast majority of cases you have maybe you need to actually handle the nothing case. The compiler makes sure you do. It also makes sure you remember not to give nothing to a function that doesn't expect it. It also reminds you to think really hard if you unsafe fromJust. Without it you can easily hand null to anyone who doesn't expect it, and forget to think about null when receiving it.