6 ms·
Why we should love null
- deleted 12y ago[deleted]
- Sharlin 12y agoYes, by all means force people to check for nulls when null is a semantically valid value! But much more importantly, in statically typed languages, allow people to distinguish between nullable and non-nullable types to prevent the most common null safety issue: accidentally passing a null value into a routine whose precondition forbids null, or storing null in a variable whose invariant forbids null.
- eudox 12y agoEssentially this. Some pointers are null, some are not, and the type system should let you express this.
- danabramov 12y agoThe author noted it, but still, all listed Optional/Maybe “disadvantages” apply to Java specifically and don't really apply to the languages where it was available from the beginning and has some kind of syntax support.
- nixpulvis 12y agoCough... Rust... Cough.
- dllthomas 12y agoIt doesn't even need syntax support, if you are comfortable with higher order functions. Java's approach of forcing the same "check, use" pattern on you makes their Optional only a modest gain. Compare with Haskell's maybe function: maybe :: b -> (a -> b) -> Maybe a -> b You provide a default result, a function from the value (if it's there) to that result type, and the optional value. There is no way to use this wrong - you can't forget to check because there is no check. Edited to add: Apparently Java does hand you the tools to do it right, if a little ugly, using ifPresent and a purpose-built Consumer. My Java is rusty, but I'll try and put an example together in a bit.
- dllthomas 12y agoSo, you can do the following: interface OverOptional<S,T> extends Consumer<T> { public S getResult(); }; Which allows you to create an anonymous class that embodies the first two arguments of maybe: Optional<Integer> maybe_int = Optional.of(7); OverOptional<Boolean, Integer> is_seven_or_empty = new OverOptional<Boolean, Integer>() { private Boolean result = true; // default public void accept(Integer i) { result = i == 7; } public Boolean getResult() { return result; } }; maybe_int.ifPresent(is_seven_or_empty); return is_seven_or_empty.getResult();
- zak_mc_kracken 12y agoNice article. I thought it was going to be superficial and rehashing common ideas but the author covers a lot of ground and languages in this overview. Of all the languages I've encountered with null/Option/Optional/Maybe in, the one I think is the most interesting is Ceylon. Ceylon supports sum types, so you can say that a type is "Account" (non nullable) or "Account|Null" (which can be abbreviated to "Account?"). The compiler will not let you dereference a nullable type, you will have to switch/type cast it first: Account? account = ... if (exists account) { // account has type Account, safe to dereference } else { // account has type Null, can't dereference } http://ceylon-lang.org/ http://ceylon-lang.org/
- zeckalpha 12y agoThis is essentially how it is in Haskell, Scala, Swift, etc.
- zak_mc_kracken 12y agoNot really. Haskell and Scala use Maybe/Option, which are monads enabling the composition of operations, even in the absence of values. This leads to a more powerful abstraction (composition) at the expense of heavier syntax and the necessity of libraries that are monad aware. Ceylon's approach is more of a middle ground with limited composability but straightforward syntax and a much simpler type system.
- theseoafs 12y agoI'm not sure what this means. In haskell you are free to use monads "the Ceylon way" if that's what you want to do; there is no reason you'd have to leverage the composability of monads if you don't want its added convenience. There is no added monadic overhead if you don't use Maybes in that way.
- wtetzner 12y agoNo, in Haskell and Scala Maybe and Option are sum types. There also happen to be monad operations defined on those types, but you don't have to use them.
- zokier 12y agoIsn't the difference between compiler-enforced null checking and Option/Maybe type (+no null) mostly superficial? Eg compare and contrast the PPL example from the article: const nullable string email = system.console?.ask_string ( "Please enter Alice's email address: " ) if email is not null then system.out.write_line ( """Alice's email address is {{email}}""" ) else system.out.write_line ( "Alice doesn't have an email address." ) end to hypothetical option type: const option<string> email = system.console?.ask_string ( "Please enter Alice's email address: " ) if email.is_present() then system.out.write_line ( """Alice's email address is {{email.get()}}""" ) else system.out.write_line ( "Alice doesn't have an email address." ) end
- darylteo 12y agoYup. It's mostly a mental hint to the programmer that "HEY this thingie could be null!".
- lomnakkus 12y agoNot with a good type system, i.e. a type system which supports algebraic types (or equivalent) and which explicitly disallows null. With such a type system you wouldn't be able to write the second example as is since string interpolation/concatenation wouldn't be defined for option<string>, only string. You would write something like: const option<string> maybeEmail = .... case maybeEmail of Some email -> system.out.write_line("....{{email}}") None -> system.out.write_line("Alice doesn't have an email address.") The thing to notice here is that we're accessing "email" and not "maybeEmail" in the Some case. This guarantees that we cannot get a null value (since the possibility has been eliminated by the case match). This simple example doesn't do it justice, however. The real benefit comes when you have functions taking option<X> vs. X arguments and/or returning option<X> vs. X. If you get a plain X (either as a return value or parameter) you know that it cannot be null. No such luck in a language which permits "null" as a value for all (reference) values. Of course algebraic data types go much further than just this. I'd encourage you to look into them in e.g. Haskell or some variant of ML.
- dbpatterson 12y agoHow can they mention Ceylon, Eiffel, Kotlin, and PPL (the latter two I've never heard of, and I've heard of a lot of programming languages) and not mention Haskell, ML, OCaml, etc? It's been mentioned in other comments, but essentially all of the supposed downsides of the Optional solution are implementation weaknesses in either the type system (allowing null anywhere, regardless of type) or runtime (there being overhead of Options) - which seems like a pretty weak argument for loving null.
- lostcolony 12y agoThis post seems somewhat strawman-ish to try and sell the author's language. No one has ever argued that the reason for nulls being a problem is because the idea of 'nothing' isn't valid; the argument has always been that they are completely invisible to the client function; that is, if you tell me functionA returns a string, I don't know if it -always- returns a string, or if it might also return a null. And you as the writer of functionA might not even know, if you're dependent on still other functions whose contracts are also ambiguous. To that end, to fix it, a type system to make the implicit null explicit, has always been the necessary answer. It -is- in fact what Option/Maybe/etc are intended to do, and provided they're used in a language that has a sufficiently robust type system, and that the language checks for non-exhaustive patterns in case matches (or similar), are still 100% effective. All of this author's issues with an Option type are explicitly with Java's implementation of it, a fact he himself notes. When he's able to show why his language provides better null protection than Haskell or Ocaml or something, I'll be interested. But as it stands, it sounds like he's just making an Option type that is a keyword in the language ("nullable a" rather than simply "a"), rather than a natural extension of the type system (Maybe a = Nothing | Just a). EDIT: This is especially galling as he's clearly aware of at least Haskell (he mentions that Haskell has the Maybe type), but then dismisses Option/Maybe at large for the reasons he dismisses Java, despite none of the arguments used against Java applying. Were the author to rewrite this, detailing the basic problem of null being that of no contractual check for it (rather than a strawman of there being no reason to have an 'empty' value), were to rewrite the Option/Maybe bit to show he understands it works fine so long as the language literally does not allow null, except as the Nothing part of the Optional/Maybe type, and instead sell PPL as being an imperative (so no need to drink the functional koolaid), simply typed (no ADTs, no covariant/contravariant OOness) language that has the same no NPE guarantees as Haskell or similar (using effectively the same mechanism; that nulls can only exist when the function contract explicitly declares them as a possibility, and that any consumer of the function must have a code branch to check for it and handle it in some way), it would actually be somewhat interesting; I could see a language like that making headway on the JVM, as it basically would come across "As simple as Java, without the NPEs", which would be a pretty easy sell.
- judk 12y agoThe remaining mystery is why someone's undergraduate-homework-level mistake got voted to the front page.
- jicea 12y agoAmong the 20 most popular programming languages listed at the Tiobe index (Basic, C, C++, C#, Java, JavaScript, Lisp, Objective-C, Perl, PHP, Python, Ruby and more) none of them has null-safety built-in. Juste some nuances for Objective-C: in Objective-C, it's perfectly valid to send any message to nil. For instance: id someObject = nil; [someObject setVisible:YES]; Is a valid code. I really appreciate this language feature, and avoid you a lot of boilerplate test. The problem is that nil as a parameter of a function (selector in Objective-C world) can trigger exception: id someObject = nil; [myArray addObject:someObject]; will trigger an exception.
- darylteo 12y agoMy gripe with that is that fail-silent scenarios can lead to some very nasty hard-to-find bugs. I've been bitten by it on several occasions. Fail fast and with lots of explosions, is the way to go for me.
- zak_mc_kracken 12y ago> and avoid you a lot of boilerplate test. You call it boilerplate testing, I call it error checking. Of course code becomes much simpler once you stop checking for error conditions, but it's still a terrible idea to do so (and I think Objective C silently accepting to send messages to nil is a never ending source for hard to find bugs)
- nawitus 12y agoI think PPL's nullable semantics are the best solution to the null problems. It makes it clear when you actually need to check for null.
- striking 12y agoType systems that incorporate "nullables" allow programmers to have the concept of "nothing was returned" but force them to check it whenever they test for data. Otherwise their programs don't compile (see the Maybe monad in Haskell). In a serious post about null, how could such a serious point be missed? For example, in Haskell: case objectMaybe of Just object -> do (your code that deals with `object` here) Nothing -> do (your code that deals with no object) Without the Nothing clause in the matching statement, this code will not compile.
- VMG 12y agoNot quite. sh-4.3$ cat test.hs main = let x = Nothing in case x of Just x -> putStrLn "something" sh-4.3$ ghc test.hs sh-4.3$ ./test test: test.hs:(2,11)-(3,40): Non-exhaustive patterns in case
- striking 12y agoAh, so it will compile but break, thank you for mentioning that.
- dllthomas 12y agoAlternatively, sh-4.3~$ ghc -Wall -Werror test.hs [1 of 1] Compiling Main ( test.hs, test.o ) test.hs:1:1: Warning: Top-level binding with no type signature: main :: IO () test.hs:3:16: Warning: This binding for `x' shadows the existing binding bound at test.hs:1:12 test.hs:3:16: Warning: Defined but not used: `x' <no location info>: Failing due to -Werror.
- kelseyfrancis 12y ago"There are more methods in Java's Optional class, but they are irrelevant for the discussion in this article." This statement represents a bad misunderstanding of the point of Optional. The fact that is has map, flatMap, and ifPresent methods is _very_ relevant to the discussion in this article.
- ehartsuyker 12y ago> This is a trivial example of a null handling situation. Nevertheless, the only way to know what will happen is to try it out because it really depends on the programming language and the compiler we use. Ask any experienced Java programmer and you will see that most of them (me included) will struggle to predict the actual behaviour without hesitation. Yes, because what I want to do is waste my time figuring out what happens when I apply functions to null values.
- ehartsuyker 12y ago> This is a trivial example of a null handling situation. Nevertheless, the only way to know what will happen is to try it out because it really depends on the programming language and the compiler we use. Ask any experienced Java programmer and you will see that most of them (me included) will struggle to predict the actual behaviour without hesitation. Yes, because what I want to do is waste my time figuring out what happens when I apply functions to null values.
- StephenGL 12y agoThese bugs are notbcauswd by null they are caused by M is understanding type systems and null itself.
- michaelochurch 12y agoThe problem with NullPointerException is that it usually comes with a high error-failure distance (that is, the failure happens later than the logical error that caused it). It can be extremely hard to debug, because null is actually an overloaded concept. It ends up meaning: 1. Like NaN, the result of an error (in some languages). 2. A pre-initialization value that "should never appear" by informal contract. 3. Missing data (especially in databases). 4. Not-applicable. 5. "There is none" (not quite the same as #3 or #4). 6. A return on an unusual (and bad) condition, like a malloc that can't. 7. (Infuriatingly common.) Errors in programming. 8. Empty collections (from Lisp, although that's probably a design error and Clojure's moving away from it.) I consider a NullPointerException for any reason to be a bug in a Java program. A more useful error message should be given. I also consider null in Scala to be something that ought to be disallowed. (In Clojure, you can't be as hard-assed about it, because nil is actually useful. But I'd still argue that NPE is always an error.) Zero-as-null is pretty awful, I agree. When you see that, it's usually as a performance optimization. For example, prices on exchanges are usually represented as integer numbers of pennies (or some smaller "microcurrency" unit) for regulatory reasons (accuracy standards set in decimal terms, disallowing floating point) and the data is unboxed for performance reasons, requiring some "illogical" sentinel value when the number isn't applicable. The effect of that is to externalize complexity to the client. For example, if you use "0" for "no bid" that's usually harmless because "no buyer" and "highest buyer at $0" are effectively equivalent. But 0 is also used for "no offer" and (if the client isn't aware of the 0's meaning) might make incorrect conclusions, since an ask of 0 (commodity given away for free) means the opposite of "no ask". The client is forced to keep track of this extra complexity-- that 0.0 doesn't mean a numerical value but means there is none. Null policy (over all the different causes of "null") is really about how the complexity is externalized to the client. Zero-as-null forces the client to deal with "zero is not zero" possibilities, which can be deadly. The best, if the API can support it, is to use Haskell-style optional types (Maybe). You can't get away from the intrinsic complexity, but you can at least require in the type system that the programmer deal with it. (Non-exhaustive pattern-match warnings should generally be treated as errors.) The problem with, say, Java's references, is that every object type has null as an additional value. Instead of a Spoon, what you actually have is an Maybe (IORef Spoon). All your (non-primitive) types end up being optional, but in a way that is usually sparsely documented.
- deleted 12y ago[deleted]
- VMG 12y ago> null is what allows us to state things like: > Alice's birth date has not yet been entered in the database (alice.birth_date = null) Better solved with Maybe (see other references in threads) > No orders have yet been placed by this customer (customer.order_list = null) Should be an empty list > The operation succeeded without any errors (error = null) The Either data type is a good solution here, because you can prevent accidentally operating on an invalid result if there was an error > There is no more beer in the fridge (fridge.beer = null) Should be 0 or an empty list
- drfrank 12y agoAs I understand it, this article misrepresents the purpose and nature of the Null Object Pattern ("NOP"). The author cites a comment that describes (very) vaguely a "hotel booking" example, expressing the idea that "None" is the null object. But "None" is not an instance of the NOP. None has no interface. You can't tell None to do something. In contrast, an acutal implementation of the NOP might be in a travel reservation service which allows you to reserve both a hotel and a rental car at the same time. Somewhere in the guts of that service there might be a Trip object. class Trip { Car car; Hotel hotel; public void Reserve() { this.car.Reserve(); this.hotel.Reserve(); } . . . } A Trip can be constructed with a NullCar when the user doesn't want to reserve a car on this trip. When NullCar::Reserve is invoked, it doesn't do anything, but Trip doesn't care, and the implementor of Trip doesn't need to remember to check its car to see if it's null before invoking it. I've encountered this misconception frequently, and it typically stems from the common understanding of object oriented programming as programming in terms of entities which represent physical objects in the real world. I deduce that the author uses this definition from the sentence "It is also interesting to note that appearances of null objects are virtually non-existent in real life." Objects aren't necessarily representations of physical entities. Objects are simply things other objects (or procedural code) can use to accomplish something without having to know how to accomplish that thing. I've found the distinction that Alan Kay has (repeatedly) made useful in helping people understand this idea: Property accessors aren't for objects, they're for Data Structures. So, when the author says that, "the basic idea of the null object pattern is exactly the same as for practices like 'use zero instead of null'", my reply is, "No, it's not called the 'Null Data Structure Pattern'." Use zero instead of null is (flawed) advice about how to write data structures, not how objects should be constructed. The NOP is not used to build objects that represent data, it is used to build objects that represent action. The author's repeated use of the GetCustomer example is inappropriate for the NOP. It's much more likely that a database record is represented using a data structure than an object. Pragmatically, if you feel the desire to ask an object if it is the NullObject, you are not writing object-oriented code, you are writing procedural code. Instead of asking if the object is the NullObject, move the branch that would be invoked if the object were the null object into a method present in the interface that the NullObject implements. Perhaps you find that you don't actually have a NullObject, after all.