9 ms·
Why Maybe Is Better Than Null
- radicalbyte 13y agoIn object-oriented modelling you often see the Null Object pattern being used to the same effect. http://en.wikipedia.org/wiki/Null_Object_pattern http://en.wikipedia.org/wiki/Null_Object_pattern It needs static analysis tools such as Code Contracts (C#) or SpringContracts (Java) to make it really robust.
- happy_dino 13y agoThe null object pattern and Maybe/Option are different concepts. The most important point: A NOP for some type T exposes T's API directly, while Maybe/Option has its own API (and the type system enforces that Option[T] can't be treated as a T).
- Evbn 13y agoGuava for Java has Optional. Very nice, except for Java's horrid syntax for generics.
- monkeyfacebag 13y agoGuava is definitely cool and I recommend it whenever I can. It's worth noting, however, that, as a Java library, it cannot provide the compile-time guarantees mentioned in the post. That is, your Option type could still be null and so you're still forced to perform run-time null checking. If you want the full benefit of Option types on the JVM, you might be better off with Scala.
- grdvnl 13y agoThe Optional<T> in Guava just forces one to explicitly check for null and then get the container object during development. A typical pattern would be: Given an object a of type Optional<MyObject>, we write: if (a.isPresent()){ MyObject o = a.get() } Of course, I could still do a.get() without evaluating isPresent() and end up with an java.lang.IllegalStateException. Here, we are "reminded" to do the null check.
- mercurial 13y agoYou can force Maybe in Haskell, and get a bad surprise at runtime as well.
- theatrus2 13y agoScala still has the problem that you can return null explicitly (since references on the JVM are nullable), but any Scala code which does this should probably be shot on sight.
- fyi80 13y agoYes, you'd need a linter to stop you from using explicit null in your code. but than be layered on top of javac with something FindBugs and its friend @Nullable. It's not "Standard" Java, but it is compiler-time enforcement.
- mercurial 13y agoNot to mention that it does not make Java magically sprout pattern-matching abilities, which makes dealing with Maybes much more pleasant and natural.
- jfb 13y agoRelatedly, I've always been fond of Objective-C's nil, for languages without strong static typesystems.
- ExpiredLink 13y ago> 1. The elimination of null. This means that all types (even reference types!) become non-nullable. Yep, problem solved, 'billion-dollar mistake' corrected (at least for future times).
- CJefferson 13y agoHow 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)
- FreeFull 13y agoI 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.
- cantankerous 13y agoNot to mention lost time spent hunting for a hidden nil value.
- spacemanaki 13y ago> they tend to crash quickly and loudly In some scenarios, this is a serious issue all by itself. My day-to-day work is mostly on Android, and eliminating nullable references altogether would eliminate some crashes, which are highly visible to the user.
- cantankerous 13y agoThe problem isn't just NULL in C. This post is talking about the entire Null/Nil reference problem across all languages that use a null-type value. This is especially a problem in dynamic languages that sling nils around....like any major modern scripting language. Checking if a value is nil before proceeding is aping what a language like Haskell does when it pattern matches against Maybe (Just a, Nothing) albeit in a post-facto bad way. Granted, you can't really make any assertions about reflecting a maybe value in the type of a language that doesn't care about types before runtime.
- yew 13y ago
- nilkn 13y agoIn 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).
- gizmo686 13y agoI disagree. If null was like Maybe, than you should be able to do: E foo; ... foo=foo.bar().bat() without having to do a null check after each function. I think that Java's approach has the worst of both worlds, because there is no way to make foo.bar().bat() safe when a function could return null. In C, for example, calling a method on a null object does not cause an error. Rather it passes in null as the 'this' value, allowing you to do you null checks within the method.
- papsosouid 13y agoI agree that C handles this much better than java. But your description is inaccurate. In C there are no methods, objects or 'this', which is far better.
- dllthomas 13y agoNull is like Nothing, nullability is like Maybe, and (.) has an implicit fromJust.
- tikhonj 13y agoThis 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 <- a b <- b return (a + b) 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/ 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.
- taeric 13y agoI 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.
- egonschiele 13y agoAha, how timely! I just wrote this post on functors and applicatives that uses Maybe as an example: http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html http://adit.io/posts/2013-04-17-functors,_applicatives,_and_...
- nickknw 13y agoCool post! Love the illustrated approach you took.
- wereHamster 13y agoLol @ "An applicative watching a functor apply a function" and the image above it :)
- kzrdude 13y agogreat illustrations.
- jayferd 13y ago_why, Maybe is better than NULL. You can program without fighting NULL.
- pubby 13y agoDoes that article title pass a type checker? How can you compare a type (Maybe) with a value (Null)? Is this instead arguing Maybe vs pointers? No, that can't be right; pointers are just type-safe as Maybe, albeit less general. I guess it's a dynamic vs static typing argument in disguise?
- tikhonj 13y agoNo. The idea is that you can get rid of null-the-value by having a Maybe type. They serve exactly the same purpose; having a null value in every type is like making each type implicitly wrapped in a Maybe. The reason we can compare the type and a value is because they serve exactly the same purpose in different ways. The core argument of the article is that we should get rid of null because Maybe does the same thing in a safer way.
- nickknw 13y agoAuthor here, that's it exactly.
- pubby 13y agoPardon my confusion, but is null referring to that of Java-style reference types, in which case the article is really about those? The value itself seems irrelevant as null is practically equivalent to Nothing. Now, if comparing the types then I'd say that Haskell's Maybe has the following improvements over Java-style optional: 1. Opt-in 2. Not tied to reference types 3. Safe extraction/"dereferencing". ie case expressions rather than Java's implicit "Maybe t -> t" All of which aren't restricted to a static type system.
- acomar 13y agoYep, you've got it exactly right. The problem for dynamic type systems is how do you enforce 3? Do you care to?
- geon 13y agoI suppose you could write a sort of safe Maybe template class in c++. You could pass in two callbacks to handle Just and Nothing respectively. It would be really ugly, but should work, right?
- weakwire 13y agoscala did it
- qb45 13y agoAnother story is that you may end up with something like: Just x -> something x Nothing -> halt_and_catch_fire # impossible In Haskell there is even a standard library function fromJust which does exactly that. One introduces this hack knowing that this particular variable will always be Just and having absolutely no way to deal with Nothing and later somebody else sees the type Maybe Foo and figures that it must be OK to put a Nothing in there.
- nandemo 13y agoWell, assuming "halt_and_catch_fire" is something like error foo or undefined, you're essentially going out of your way to circumvent the type system. It's possible to do it, but it's frowned upon.
- deleted 13y ago[deleted]
- nilkn 13y agoJava is really not the greatest example here. In fact, the source article here is basically saying as much by calling into question some of the Java conventions. I think you'll find in a language like Haskell that a lot more information can be embedded in types than in something like Java.
- dllthomas 13y agoIf the database structure is encoded in your type system, and all your code that accesses the database (including the initial population of the database) is checked against that encoding, the compiler can absolutely statically check the correctness of code that relies on the structure and contents of the database. Several of the mainstream Haskell database packages do this.
- drorweiss 13y agoSure, I will look at Haskell. Still, IMO this may be true in theory. In practice, most of the errors we encounter come from incomplete understanding of the input we're dealing with. These are many little rules that are very hard to formalize.
- Peaker 13y agoMost of the errors you remember encountering, I bet.
- dllthomas 13y agoIncomplete understanding of input or domain is certainly a cause of problems, and I don't know that there is a way the type system can help in the general case. With regard to databases in particular, though, it is comparatively easy to constrain things so that you don't have that problem, and enlisting the type system there makes sense. There are other areas that have useful approaches as well - consider protocol buffers, for instance. I prefer to let the type system catch what it can, and use tests for where it can't - often type info can be leveraged to help in the testing, too: I recommend checking out quickcheck if you haven't.
- dscrd 13y agoIf you work on types that might be null, you're bloody careful or you're in the wrong profession. We're not calling incompetent programmers a gazillion dollar mistake, even though they probably have caused as much.
- papsosouid 13y agoLook everyone, dscrd has solved the problem of software having bugs. Just be careful! Man, why didn't anyone think of that before? The software industry is going to be so much nicer now that you eliminated bugs.
- dscrd 13y agoThanks! Where can I pick up my Nobel?
- venomsnake 13y agoNot everyone has the profession of herding spherical cows in vacuum ... in the real world lots of stuff can bait you in the ass.
- virtualwhys 13y agothis...makes me laugh, thanks ;-) I would agree that the real world can bait one in the ass, out of the blue, just, what the, things are trying to eat my ass! As for herding spherical cows and the vacuum, I'll do my best: val maybeCow = Some(1) // SomeOne is the speherical mu (i.e. you) maybeCow getOrElse 0 // Cow here becomes 1 with the universe Were maybeCow initialized with a None value, then into the vacuum it goes...and out it comes with a safe 0 to keep order in our [application] universe.
- dscrd 13y agoI'm very amused. About 80% of my career has been in the "real world", so I have actually have some authority in calling this. No power in the world can subvert incompetence.
- geon 13y ago
- kelnos 13y agoSo maybe this is just a terminology thing, but, isn't Maybe the same thing as: 1. By default all types are non-nullable. 2. You explicitly mark if you expect that a value could be null. 3. The compiler helps you by either making sure you check for null on those values you mark, or by doing some magic (like Scala does) that will always return null for expressions where one of the values is actually null. Is the reason we call it Maybe/Option/whatever just to disambiguate with the traditional use and lack of safety in what pretty much all languages use "null" for? Or is there a distinction I'm missing?
- MichaelGG 13y agoThe difference is that Maybe or Option is something you can implement in library code, it does not require any special compiler implementation. Also, most languages require something to be a reference or pointer to be null. You can't have a plain int or structure be null.
- tikhonj 13y agoYes, it's the same idea. The main difference is that Maybe is just a normal type in languages like Haskell and OCaml--you do not need any especial compiler support for it. So to use Maybe, all you need from the compiler is to not have nulls everywhere. Since you don't need language support for it, your language is simpler and the Maybe behavior is part of a library. This also ensures that you Maybe values behave as first-class citizens. You can do anything with the Maybe type that you could with any other type, because that's all it is. For example, this means that you can nest them: have a Maybe<Maybe<A>> value, for example. It also means Maybe can play well with other libraries; for example, inn Haskell, it works immediately with the alternation operator: result = tryA "foo" <|> tryA "bar" <|> tryB Part of the beauty is that <|> is an operator that represents alternation for a whole bunch of other types as well. There are a whole bunch of other functions like this. So: yes, you can have language support for it. But just having it as a normal type makes the language simpler and ensures you have full generality. The only thing that you need from your language is to get rid of null.
- salmonellaeater 13y ago
- hmottestad 13y agoNull != None Null means unknown. Take a look at the difference between open and closed world systems.
- geon 13y agoNull would mean whatever the programmer inteded it to mean. In the C world it sees to me it tends to mean "nothing", while in SQL it usually means "unknown".
- rtfeldman 13y agoGreat article! I'm especially glad to see "what have we gained?" in the FAQ, as it is probably the most frequently asked question I've heard. OP - I noticed you were thorough enough to mention both Fantom and Kotlin in one section, so for the preceding section you might want to note that CoffeeScript also has a safe-invoke operator like Groovy's.
- nickknw 13y agoThanks! And I'll make a note for myself to put CoffeeScript in there too.
- wellpast 13y ago@Nullable plus static code analysis gives me quite a bit of static-time feel goodness in Java without any need for Optional/Maybe.
- vorg 13y ago> Grōōvy isn’t really about statically enforcing things They added a statically-compiled mode to Grōōvy last year. Most users like Grails don't use it yet, but it's there to try out and you can report any problems to the Grōōvy issue tracker.
- Bjoern 13y agoDoes anyone have a nice implementation of the Maybe Monad for C++ ?
- acomar 13y agoThere are a ton floating around, they just aren't terribly useful because every type is by default nullable. So even standard library functions can return null. You might be able to make your own code a little safer, but you still have to null check everything.
- CountHackulus 13y agoMaybe IS Null, they're just two different implementations of monads.
- geon 13y agoNot really. Maybe might be null. Null is always null, and it can be anywhere.
- implicit 13y agoI've been working with engineers writing production Haskell for the first time; its lack of a "null" concept is proving to be absolutely incredible. In a high-uptime production environment, it's at least as valuable as Haskell's enshrinement of purity. We have a convention that all recoverable errors be captured in Either or Maybe. This policy is paying off in a huge way because the type system forces us to think about what error cases mean. This is driving us to write substantially higher-quality code than I've written with other tools.
- nickknw 13y agoGreat to hear a real-world example of the concrete benefits, thanks!
- anuraj 13y agomaking null fuzzy may save your program from a crash, but it also makes execution unpredictable. I would prefer a crash - which would lead to better localization and bug fixing - than indeterminate behaviour which is hard to debug and maintain.
- stickfigure 13y agoI can't believe there is no mention of Ceylon here. Ceylon has an elegant approach to this using union types. Typesafe null and flow-dependent typing There's no NullPointerException in Ceylon, nor anything similar. Ceylon requires us to be explicit when we declare a value that might be null, or a function that might return null. For example, if name might be null, we must declare it like this: String? name = ... Which is actually just an abbreviation for: String|Null name = ... An attribute of type String? might refer to an actual instance of String, or it might refer to the value null (the only instance of the class Null). So Ceylon won't let us do anything useful with a value of type String? without first checking that it isn't null using the special if (exists ...) construct. void hello(String? name) { if (exists name) { print("Hello, ``name``!"); } else { print("Hello, world!"); } } From http://ceylon-lang.org/documentation/1.0/introduction/ http://ceylon-lang.org/documentation/1.0/introduction/