There is no null; Maybe/Option types

Hi Cédric, I saw your post on Maybe types wondering what the point of it is and why anyone would want it. I thought I'd reply by email as I don't want to infringe on your freedom of expression. If you find that I'm saying things you already know, please scroll down to the bottom, which addresses your final question. I'm CC'ing the Haskell mailing list. I wrote a lot and I might as well share it. Quotes below are from: http://beust.com/weblog/2010/07/28/why-scalas-option-and-haskells-maybe-type...
If you read the voluminous material that describes the concepts behind the Option class, there are two main benefits:
- It saves you from NullPointerException - It allows you to tell whether null means “no object” or “an object whose value is null”
The second point is true, as you probably already understand:
- Nothing is different from False. - They also have distinct, completely un-unifiable types. Nothing is type Maybe something, False is type Bool. - Maybe Int is also distinct from Maybe String. The latter point is true in a sense, but I think the problem and the solution aren't the same for Haskell and Java. I can't speak for Java because I don't know it, I know C#, which I know from a lot of experience does have nullable objects and thus null pointer exceptions. I assume everything below can be applied to Java, please tell me if not. - *No* value in Haskell can be null/empty. - *All* values in C# can be null/empty. - Therefore if C#'s values were completely explicit, and they might be defined, in Haskell, as: - data CObject a = TheValue a | Null - I'm assuming you know a bit of Haskell or can just figure it out easily enough. Read as: the type CObject, for all types, a, can be represented by either a value of type a, or null. - So a value of type int can be inhabited by either 123 or null. - Notice, of course, that this is the same as the definition of Maybe: data Maybe a = Just a | Nothing. - Therefore, in Haskell's context, it's not that Maybe saves you from null pointers, it's that it *allows you to express* nullability in a language without implicit nullability, which is default for all values in C#. I suspect the crux of the problem is that Java and C# have implicit nullability. It's also worth noting that the Maybe type in Haskell *can* *also* cause something similar "null pointer exceptions"; if you use the function fromJust, which just takes from a Maybe value and throws an exception if there's nothing in it. And, indeed, this function is a very common cause of exceptions in Haskell. A simple Google shows 870 results of this exception: http://www.google.com/#q="Exception:+Maybe.fromJust%3A+Nothing"http://www.google.com/#q=%22Exception:+Maybe.fromJust%3A+Nothing%22Also similar is use of the head function, which takes the first element of a list or throws an exception is the list is empty: http://www.google.com/#q="Exception:+Prelude.head%3A+empty+list"http://www.google.com/#q=%22Exception:+Prelude.head%3A+empty+list%22. 4,610 results. Most of the (non-IO) runtime errors I get using Haskell software is due to head or fromJust, it's actually quite annoying. That is the technical aspect and some background, for what it's worth. val result = map.get( "Hello" )
result match { case None => print "No key with that name!" case Some(x) => print "Found value" + x }
See what’s going on here? You avoid a NullPointerException by… testing against null, except that it's called None. What have we gained, exactly?
The worst part about this example is that it forces me to deal with the null case right here. Sometimes, that's what I want to do but what if such an error is a programming error (e.g. an assertion error) that should simply never happen?
I believe here lies the problem. It's true; if your method expects a value not to be null then you shouldn't have to deal with it. - If the value is never ever supposed to be null, then you should be given a non-nullable value. - If the value is supposed to be nullable at some point, then your function is explictly giving the opportunity for optionality and should support it. If it barfs on a null value then it's not really satisfying the deal made with the caller that it supports nullable values. Unfortunately this is impossible in Java and C# because you can't have non-nullables. You also can't tell everyone to never use null and use a Maybe value, so C# or Java are more or less screwed on this point.
And you know what construct does exactly this? NullPointerException! Try to reference a null pointer and that exception will be thrown. It will make its way up the stack frames since you probably never catch it anywhere (nor should you) and it will show you a clear stack trace telling you exactly what happened and where.
The solution proposed for Java is for the method to barf on bad input at runtime. The solution of OCaml/Haskell is to barf at the caller of the function at development time; you're using the function incorrectly, and the developer's realised that he's using it incorrectly and immediately fixes it; the code never made it into testing or production. How much time did this save? I don't think anyone would argue that finding out later rather than now that we screwed up is desirable.
In this case, I just want to assume that I will never receive null and I don't want to waste time testing for this case: my application should simply blow up if I get a null at this point.
:t head
:t listToMaybe
You can do that with fromJust, but it's generally considered bad practice, at least in the Haskell development world. Using fromJust brings enforcing constraints away from the compiler and into the hands of the developer who is flawed, busy and confused most of the time. These types of functions are called partial functions. A total function is a function that handles every case of a value given to it. So: head :: [a] -> a listToMaybe :: [a] -> Maybe a
head [] *** Exception: Prelude.head: empty list listToMaybe [] Nothing
The former is partial, the latter is total. Code that uses partial functions is inherently partial in its entirety, leaving enforcing invariants on the programmer which could otherwise have been done by the compiler. *Static*: In Fantom, the fact that a variable can be null or not is captured
by a question mark appended to its type:
Already covered; we both know Option/Maybe do this.
- *Runtime*: The second aspect is solved by Fantom's "safe invoke" operator, ?.. This operator allows you to dereference a null pointer without receiving a null pointer exception: // hard way Str? email := null if (userList != null) { user := userList.findUser("bob") if (user != null) email = user.email }
// easy way email := userList?.findUser("bob")?.email
Note how this second example is semantically equivalent to the first one but with a lot of needless boiler plate removed.
So, can someone explain to me how Option addresses the null pointer problem better than Fantom's approach?
Use of Fantom's save invoke and Maybe are more or less the same. -- Hard way email = if userList /= Nothing then let user = findUser "bob" (fromJust userList) in if user /= Nothing then getEmail (fromJust user) else Nothing else Nothing Note that I have to use fromJust because there is no implicit nullability. -- Easy way email = userList >>= findUser "bob" >>= getEmail This is semantically equivalent to the first one. There is also syntactic sugar for the above, for when you're not just chaining, but doing something complicated: -- Sugared way email = do list <- userList user <- findUser "bob" list email <- getEmail user return email If I'm using list or user or email more than once, this style is very useful. This is exactly equivalent to the above, it de-sugars into using the
=function, which is pretty much the same as your use of ?., in this context. Cases and functions are just another way of accessing maybe values when you explicitly want to handle the null case.
λ> maybe 4 (+2) (Just 5) 7 λ> maybe 4 (+2) Nothing 4 λ> case Just 5 of Just x -> x * 2; Nothing -> 0 10 I'm not saying it's the only way or the best way. It's certainly nice, though. P.S. Question: what would this code do and what would the compiler tell you about it? Str? email := null email = userList.findUser("bob").email

On Fri, 22 Apr 2011, Christopher Done wrote:
Use of Fantom's save invoke and Maybe are more or less the same.
-- Hard way email = if userList /= Nothing then let user = findUser "bob" (fromJust userList) in if user /= Nothing then getEmail (fromJust user) else Nothing else Nothing
In idiomatic Haskell you would write case userList of Nothing -> Nothing Just plainUserList = let user = findUser "bob" plainUserList ... since (userList /= Nothing) requires an Eq instance without need and it requires fromJust. Or was there an educational purpose to write it with (/= Nothing) ?

On 22 April 2011 21:26, Henning Thielemann
In idiomatic Haskell you would write
case userList of Nothing -> Nothing Just plainUserList = let user = findUser "bob" plainUserList ...
since (userList /= Nothing) requires an Eq instance without need and it requires fromJust. Or was there an educational purpose to write it with (/= Nothing) ?
Educational; it was just to make it look like and have similar meaning (as I perceived it) to the Fantom code. (I'd never write code like this; I use case analysis and avoid if.)

On Fri, 2011-04-22 at 21:26 +0200, Henning Thielemann wrote:
On Fri, 22 Apr 2011, Christopher Done wrote:
Use of Fantom's save invoke and Maybe are more or less the same.
-- Hard way email = if userList /= Nothing then let user = findUser "bob" (fromJust userList) in if user /= Nothing then getEmail (fromJust user) else Nothing else Nothing
In idiomatic Haskell you would write
case userList of Nothing -> Nothing Just plainUserList = let user = findUser "bob" plainUserList ...
since (userList /= Nothing) requires an Eq instance without need and it requires fromJust. Or was there an educational purpose to write it with (/= Nothing) ?
Using 'more advanced haskell' email = getEmail =<< findUser "bob" =<< userList or email = do ul <- userList user <- findUser "bob" ul getEmail user Regards PS. If you worried what is (=<<) it is generalised maybe (i.e. if you observe maybe, concatMap etc. follow similar pattern - it turns out it is common pattern and it is worth to be generalised).

On Fri, 22 Apr 2011, Maciej Marcin Piechotka wrote:
On Fri, 2011-04-22 at 21:26 +0200, Henning Thielemann wrote:
In idiomatic Haskell you would write
case userList of Nothing -> Nothing Just plainUserList = let user = findUser "bob" plainUserList ...
since (userList /= Nothing) requires an Eq instance without need and it requires fromJust. Or was there an educational purpose to write it with (/= Nothing) ?
Using 'more advanced haskell'
email = getEmail =<< findUser "bob" =<< userList
This is what Christopher Done wrote some lines below the code, that I quoted.

On Fri, 2011-04-22 at 22:11 +0200, Henning Thielemann wrote:
On Fri, 22 Apr 2011, Maciej Marcin Piechotka wrote:
On Fri, 2011-04-22 at 21:26 +0200, Henning Thielemann wrote:
In idiomatic Haskell you would write
case userList of Nothing -> Nothing Just plainUserList = let user = findUser "bob" plainUserList ...
since (userList /= Nothing) requires an Eq instance without need and it requires fromJust. Or was there an educational purpose to write it with (/= Nothing) ?
Using 'more advanced haskell'
email = getEmail =<< findUser "bob" =<< userList
This is what Christopher Done wrote some lines below the code, that I quoted.
Ups. Sorry - I didn't have all the context. Regards

On 4/22/11 3:26 PM, Henning Thielemann wrote:
On Fri, 22 Apr 2011, Christopher Done wrote:
Use of Fantom's save invoke and Maybe are more or less the same.
-- Hard way email = if userList /= Nothing then let user = findUser "bob" (fromJust userList) in if user /= Nothing then getEmail (fromJust user) else Nothing else Nothing
In idiomatic Haskell you would write
case userList of Nothing -> Nothing Just plainUserList = let user = findUser "bob" plainUserList ...
Well, in *idiomatic* Haskell you'd write: plainUserList <- userList user <- findUser "bob" plainUserList getEmail user Or even just: getEmail =<< findUser "bob" =<< userList (As others have mentioned a few times.) But the greatest thing about Maybe is that you don't *have* to write code in monadic style. Because Maybe makes explicit the null-pointer shenanigans in other languages, you can simply unwrap the Maybe and pass around the raw value instead of letting Nothing permeate your whole program. Spending all your time in the Maybe monad is just as bad as spending all your time in the IO monad. Purity begets clarity! Bob Harper has a few things to say[1] about using equality tests instead of case analysis, and I agree with him. [1] http://existentialtype.wordpress.com/2011/03/15/boolean-blindness/ -- Live well, ~wren

wren ng thornton
But the greatest thing about Maybe is that you don't *have* to write code in monadic style. Because Maybe makes explicit the null-pointer shenanigans in other languages, you can simply unwrap the Maybe and pass around the raw value instead of letting Nothing permeate your whole program. Spending all your time in the Maybe monad is just as bad as spending all your time in the IO monad.
Unless you're saying that I'm one of the worst Haskell programmers in the world, you are totally wrong. Monads are an abstraction for convenience and beauty in expression, not for encapsulating bad habits. Particularly there is nothing wrong with writing 90% of your code in monadic style, which is what I do, often combining three or more monads. And even if I don't use Maybe in monadic style, I often use combinators like 'maybe'. Monadic style to avoid explicit wrapping, and combinators to avoid explicit unwrapping. What exactly is wrong with that? Why would you /not/ use (>=>) to calculate the fourth root from a monadic square root function? fourthRoot :: (Alternative f, Integral i, Monad f) => i -> f i fourthRoot = mSqrt >=> mSqrt
Purity begets clarity!
All Haskell code is pure (in the sense of referential transparency), including monadic code, even in the IO monad. Greets, Ertugrul -- nightmare = unsafePerformIO (getWrongWife >>= sex) http://ertes.de/

On 4/26/11 9:55 AM, Ertugrul Soeylemez wrote:
wren ng thornton
wrote: But the greatest thing about Maybe is that you don't *have* to write code in monadic style. Because Maybe makes explicit the null-pointer shenanigans in other languages, you can simply unwrap the Maybe and pass around the raw value instead of letting Nothing permeate your whole program. Spending all your time in the Maybe monad is just as bad as spending all your time in the IO monad.
Unless you're saying that I'm one of the worst Haskell programmers in the world,
Hardly. Never having seen your code that would be most uncharitable.
you are totally wrong.
I'm not sure I see how that follows.
Monads are an abstraction for convenience and beauty in expression,
This I agree with...
not for encapsulating bad habits.
...this, not so much. Though I would phrase my negation differently. It is not the habits which are encapsulated, it is merely the effects. And the effects which are encapsulated are not "bad", per se. For example, GHC uses a prodigious amount of mutation under the covers, and we're all glad it does! The point is rather that effects like these make reasoning difficult. Thus, in order to keep our programs reasonable it behooves us to find ways of restricting the scope of these effects ---not because the effects are an evil taint that cannot be allowed to spread, but--- because it is easier to reason about code which does not make excessive use of them. Effects like mutation, failures, capturing the current continuation, etc are all just tools. And like any tools they can be used both for great good and for great evil. Knowing that some segment of code does not use them means that we needn't worry about there being devils there, but it also means we don't have to worry about there being angels. In my experience, the angels can wreak just as much havoc as the devils. Angels are often inscrutable and too clever to be deciphered by mere mortals; and angels have ingenious ways of working around laws I would like to set in stone. When I would like to exclude certain kinds of behavior, divine grace can be just as damaging as a trickster. -- Live well, ~wren

Most of the (non-IO) runtime errors I get using Haskell software is due to head or fromJust, it's actually quite annoying.
Just singling this one out because I've heard it before. I've never gotten a runtime error from these. It seems quite easy to just not use these functions. It's already basically been said, but to add more personal experience: For the original blog post, I think the title is pretty easily falsifiable just from practical experience: haskell's Maybe *did* save me from NPEs and that's why I don't see them. The problem is not that a key lookup may fail in a map or whatever and now you have to propagate that Nothing, the problem is that they pop out of just about any code at just about any time and are not covered by the tests because you can't test all code in all ways, so production apps wind up crashing from them. That's the thing I see all the time in java, and don't see in haskell. And I don't think it's just redefining the problem either, I only rarely get "unexpected Lefts" once the app has gotten past the tests. So that's the practical result I actually care about.

Evan Laforge
Most of the (non-IO) runtime errors I get using Haskell software is due to head or fromJust, it's actually quite annoying.
Just singling this one out because I've heard it before. I've never gotten a runtime error from these. It seems quite easy to just not use these functions.
Unfortunately, it is quite easy to just use them, too :-) Sometimes I just *know* that this list will never be empty, or that that Maybe will never be nothing - but often I'm wrong. Maybe is in a sense easier, as 'Maybe a' and 'a' are different types, while there isn't a similarly elegant type distinction between lists that may be empty and lists with at least one element. Maybe is really quite brilliant. -k -- If I haven't seen further, it is by standing in the footprints of giants

Ketil Malde schrieb:
Maybe is in a sense easier, as 'Maybe a' and 'a' are different types, while there isn't a similarly elegant type distinction between lists that may be empty and lists with at least one element. Maybe is really quite brilliant.
type NonEmptyList a = (a, [a]) data NonEmptyList a = NonEmptyList a [a] I use the second one frequently for quick check testing and especially for testing on infinite lists. Unfortunately, the Prelude functions like 'cycle' (for input and output) and 'group' (for sublists of the result list) do not use that type.

On Sat, Apr 23, 2011 at 5:57 PM, Ketil Malde
Evan Laforge
writes: Most of the (non-IO) runtime errors I get using Haskell software is due to head or fromJust, it's actually quite annoying.
Just singling this one out because I've heard it before. I've never gotten a runtime error from these. It seems quite easy to just not use these functions.
Unfortunately, it is quite easy to just use them, too :-) Sometimes I just *know* that this list will never be empty, or that that Maybe will never be nothing - but often I'm wrong.
That's the thing I was commenting on... I do a lot of things wrong, but maybe somewhere I'm doing something right, because for whatever reason this never comes up for me. The only times I can think of are 'maximum' that I know will complete because I wrote 'maximum (0 : stuff)' and "split" functions, which are guaranteed to return at least one element. As Henning points out, (a, [a]) is an explicit type for those, but usually I just trust the postcondition of the function and match right away on its output. I have lots of 'maybe [] id' or whatever, but it's always cases where I expect a Nothing so I have an escape route planned.
participants (9)
-
Christopher Done
-
Ertugrul Soeylemez
-
Evan Laforge
-
Henning Thielemann
-
Henning Thielemann
-
Ketil Malde
-
Maciej Marcin Piechotka
-
Maciej Piechotka
-
wren ng thornton