Live data from Hacker News

Why Maybe Is Better Than Null

nickknowlson.com

11–20 of 133 posts

Re: Why Maybe Is Better Than Null

#11
post #8

How many problems are actually caused by null (in particular, how many billions of dollars?) While pointers which point into the wrong place for various reasons (off end of array, previously freed memory) cause horrible issues to this day, I can't personally remember ever having a serious issue with a null pointer (they tend to crash quickly and loudly, because in all modern OSes dereferencing NULL segfaults)

I don't know about it in terms of money loss, but generally having missed checks caught at compile time rather than having the program crash is a good thing.

Not to mention lost time spent hunting for a hidden nil value.

Re: Why Maybe Is Better Than Null

#12
In comparison to Haskell, null in Java is similar to adding an implicit Maybe a instance for every type a.

In doing so, Java makes it just that much easier to create bottom values when they are not desired (e.g., dereferencing a null pointer).

Re: Why Maybe Is Better Than Null

#13
This article makes it seem like you'd have to explicitly check whether a Maybe value is Nothing when you use it. This is certainly safe, but it's also very awkward; as a contrived example, adding two numbers would look like this:

    case a of
      Nothing -> Nothing
      Just a  -> case b of
        Nothing -> Nothing
        Just b  -> a + b
This is quite a bit of boilerplate hiding the expression that actually matters--a + b! Moreover, whenever you have code that creeps steadily to the right, it means you either messed up or missed an abstraction.

It turns out that this pattern--do a computation if all the values are present, but return Nothing if any of them are Nothing--happens very often. Happily, we can get some nice syntax for Maybe computations like this using do-notation in Haskell or for-comprehensions in Scala:

    do a 
This is much better! It makes even more sense for more complicated expressions, especially when later results depend on values of earlier ones. However, for a simple example like a + b, it's still quite a bit of boiler plate; we can certainly do better! Here are two alternatives using functions from the Control.Applicative module:

    (+)  a  b
    liftA2 (+) a b
The important idea here is that both versions somehow "lift" the (+) function to work over Maybe values. This just means they create a new function with checks for Maybe built in. This is great because it saves all the boilerplate above and nicely abstracts away most of the null checks while preserving safety. But the syntax is still a bit awkward. Happily, if you don't mind using a preprocessor[1], you can get some very nice syntax called "idiom brackets":

    (| a + b |)
[1]: https://personal.cis.strath.ac.uk/conor.mcbride/pub/she/

The computation inside the (| and |) is lifted over Maybe, just like the two previous examples. I think this is the clearest option here: it has the least syntactic overhead, and the base expression--a + b--is very easy to read. They also have the advantage of nesting, so you can express a + b + c, where you want a null check for all three variables, as:

    (|(|a + b|) + c|)
This isn't perfect, but I think it's still very easy to follow. It might be better if the (| and |) were a single character, something like this:

    ⦇⦇a + b⦈ + c⦈
However, some people really don't like Unicode symbols in their code :(. Happily, you can have the source look like (|foo|) and have Emacs replace it with ⦇foo⦈ without actually changing the code. It's basically Unicode syntax highlighting. I think this leads to the most readable code so far.

So my main point is that you can abstract out the common case where you check for Nothing and make the whole expression Nothing if any sub-expression is. This saves quite a bit of typing and much more importantly makes the resulting code far easier to read.

Another really cool part is that all these syntax forms and functions are not specific to Maybe--they actually work for a whole bunch of different types. So you would not be bloating your language by including special features just for safely checking nulls; these features are much more general.

Re: Why Maybe Is Better Than Null

#14

How many problems are actually caused by null (in particular, how many billions of dollars?) While pointers which point into the wrong place for various reasons (off end of array, previously freed memory) cause horrible issues to this day, I can't personally remember ever having a serious issue with a null pointer (they tend to crash quickly and loudly, because in all modern OSes dereferencing NULL segfaults)

I've worked with a code base in C++ where the code base would accumulate significant amounts of

    if (argumentX == null)
        return null;
at the top of function signatures. It was just defensive programming. Maybe argumentX couldn't be null, but it would take time to figure that out (sometimes I did that, though). More code means harder to read and maintain, thus costing dollars.

This would also be contagious: If a piece of code checks if X is null, you'll assume that X can be null, whether or not that's true.

I'd certainly prefer to be able to reason about the code with the safe assumption that certain things cannot be null.

Re: Why Maybe Is Better Than Null

#15
post #13

This article makes it seem like you'd have to explicitly check whether a Maybe value is Nothing when you use it. This is certainly safe , but it's also very awkward; as a contrived example, adding two numbers would look like this: case a of Nothing -> Nothing Just a -> case b of Nothing -> Nothing Just b -> a + b This is quite a bit of boilerplate hiding the expression that actually matters--a + b! Moreover, whenever…

Another way to go would be to use some functions that conceal the mechanics of Maybe. For instance:

    (fromMaybe 0 a) + (fromMaybe 0 b)
This will always produce a number and it's pretty clear why you'd get the one you'd get.

Re: Why Maybe Is Better Than Null

#16
I think the largest hurdle I have getting running with maybe, is that it just isn't in my fingers yet. As such, it can really slow you down if you were trying to build a bare bones prototype without layering. Which, given the popularity of dynamic languages, I would think is fairly common.

That is, if you are going to use Maybe, you either want to do so from the beginning, or you want a good layer of abstraction between where the value is optional and where it is not. Moving something from guaranteed to optional is a bit more combersome with Maybe.

Also, I have gotten really used to "truthy" values.

Re: Why Maybe Is Better Than Null

#17

How many problems are actually caused by null (in particular, how many billions of dollars?) While pointers which point into the wrong place for various reasons (off end of array, previously freed memory) cause horrible issues to this day, I can't personally remember ever having a serious issue with a null pointer (they tend to crash quickly and loudly, because in all modern OSes dereferencing NULL segfaults)

I've worked with a code base in C++ where the code base would accumulate significant amounts of if (argumentX == null) return null; at the top of function signatures. It was just defensive programming. Maybe argumentX couldn't be null, but it would take time to figure that out (sometimes I did that, though). More code means harder to read and maintain, thus costing dollars. This would also be contagious: If a piece o…

Even if it can't be null now the calling code may change in the future. And did you check all the possible error conditions (e.g. failed allocation). If you are relying on a non-null call best to check every time.

Re: Why Maybe Is Better Than Null

#18
post #13

This article makes it seem like you'd have to explicitly check whether a Maybe value is Nothing when you use it. This is certainly safe , but it's also very awkward; as a contrived example, adding two numbers would look like this: case a of Nothing -> Nothing Just a -> case b of Nothing -> Nothing Just b -> a + b This is quite a bit of boilerplate hiding the expression that actually matters--a + b! Moreover, whenever…

You can also make Maybe an instance of various typeclasses for even nicer syntax:

    instance Num a => Num (Maybe a) where
        (+) = liftM2 (+)
        (-) = liftM2 (-)
        (*) = liftM2 (*)
        abs = liftM abs
        signum = liftM signum
        negate = liftM negate
        fromInteger = Just . fromInteger

    > Just 4 + 2 * Just 6
    Just 16
    > Nothing * 42
    Nothing
Notice how the fromInteger method allows you to freely mix Maybe and non-Maybe numbers.

Re: Why Maybe Is Better Than Null

#19

How many problems are actually caused by null (in particular, how many billions of dollars?) While pointers which point into the wrong place for various reasons (off end of array, previously freed memory) cause horrible issues to this day, I can't personally remember ever having a serious issue with a null pointer (they tend to crash quickly and loudly, because in all modern OSes dereferencing NULL segfaults)

[deleted]

Re: Why Maybe Is Better Than Null

#20
post #13

This article makes it seem like you'd have to explicitly check whether a Maybe value is Nothing when you use it. This is certainly safe , but it's also very awkward; as a contrived example, adding two numbers would look like this: case a of Nothing -> Nothing Just a -> case b of Nothing -> Nothing Just b -> a + b This is quite a bit of boilerplate hiding the expression that actually matters--a + b! Moreover, whenever…

You can also make Maybe an instance of various typeclasses for even nicer syntax: instance Num a => Num (Maybe a) where (+) = liftM2 (+) (-) = liftM2 (-) (*) = liftM2 (*) abs = liftM abs signum = liftM signum negate = liftM negate fromInteger = Just . fromInteger > Just 4 + 2 * Just 6 Just 16 > Nothing * 42 Nothing Notice how the fromInteger method allows you to freely mix Maybe and non-Maybe numbers.

I don't have a whole lot of Haskell experience ... but these "Maybe" unwrapping functions are rare right? Like a null check I'd think that most of the time you make the check when you get the unreliable input or allocation or whatever, and thereafter in the guts of you program you have the certainty that the input has been "checked".
Post reply on HN