5 ms·
I remember running into nil a lot when trying to use Clojure. My immediate sensibility was very "yuck". It allows for nice chaining of APIs but it seems to real
by rtpg 5y ago
I remember running into nil a lot when trying to use Clojure. My immediate sensibility was very "yuck". It allows for nice chaining of APIs but it seems to really just mess up the notion of optionality so much that I'm curious about how often it's a bug generator.
I understand that the sort of spaghetti-code from constant nil checking would be painful, but I can also imagine a lot of hidden bugs when it comes to dealing with things like user-provided data. Would love to hear the experience from Lispers who also have experience with "null"-y issues in other languages.
- brundolf 5y agoPosted under another comment, but I've personally experienced it being a big problem where a Lisp interfaced with another language: https://news.ycombinator.com/item?id=27878200 https://news.ycombinator.com/item?id=27878200
- didibus 5y agoClojure doesn't have this problem, in Clojure nil is not the empty list, and the empty list is not nil. ;; Nil and empty list are different (nil? '()) ;> false ;; Empty list is not falsy (if '() true false) ;> true ;; Only nil is falsy (if nil true false) ;> false ;; Prints as empty list (pr '()) ;> ()
- brundolf 5y agoGood decision on their part. Unfortunately ours was CL-inspired
- didibus 5y agoI've got professional experience with null in Java, C#, and C++, as well as Clojure. From my experience "nil" in Clojure is the least "issue creating" of the bunch. Generally when "nil" is an issue in Clojure, it's actually from the Java side, where Java doesn't "do the right thing" for "nil" and just throws a "null pointer exception" instead. In Clojure it's mostly a problem when it hides that you just aren't getting something because of a bug, as opposed to because it truly isn't there. The most common one for me being map lookups: (get m :fooo) ;> nil Now everything after will "work", because everything handles "nil" input and generally does "the right thing", like treat it as false, or returns nil if given nil, or treats it as identity, etc. But you actually just messed up the key and that's why you are getting "nil", where you wanted to do: (get m :foo) ;> "foo" I'd say this is the most annoying case for "nil" for me in Clojure.
- christophilus 5y agoAgreed. The problem often presents itself downstream, or in a totally different place from where it originated.
- gnud 5y agoThis is exactly the 'null' bug, and the problem that's solved with a strongly-typed language and an `Optional<T>` or `Maybe T`. Then you are forced to deal with the fact that you might not have a value, right away.
- didibus 5y agoOptional<T> actually has the same issue I think. Clojure properly handles "nil", the problem is not that you need something to force you to deal with not having a value right away, the problem is that Clojure automatically deals with the value not having a value right away for you. Imagine you have this: Optional<String> get(String k) { ... } get("fooo").orElse("not found"); This is the same bug, the bug being it's normal that "fooo" is missing, you should have written "foo" which is the real place where the data is supposed to be. Or imagine for any other reason `get` returns an Optional.empty() when it shouldn't. It's kind of the flip side of "null". Normally nulls are bad because of nullPointerException and people forgetting to handle null. Clojure in general handles null everywhere, and so you won't get nullPointerException, instead things will just work with whatever defaults makes sense for everything that comes after and is getting the null. That makes it harder to realize that the null is there not because it missing, but because you have a bug which makes it look like something is missing when it should be there.
- blacktriangle 5y ago(get m :foo :not-found) Problem solved.
- didibus 5y agoI wouldn't say this totally solves it: (get m :fooo :not-found) Is still a bug that'll be hard to debug, because your code will most likely handle :not-found and believe that it is truly not-found for some reason, but it's not, you messed up the keyword.
- blacktriangle 5y agoHere's the realization that got me over the "yuck" of nil punning, well two realizations. First, the whole billion dollar mistake is nil POINTERS, not the value nil. Nil in lisp is a value. This is not really a problem, and in fact there's even a design pattern of the null object pattern (https://en.wikipedia.org/wiki/Null_object_pattern https://en.wikipedia.org/wiki/Null_object_pattern) to reproduce this behavior in OOP languages. Second, in CL, there really is no such thing as nil. Nil is the empty list, they are the same thing. you could search/replace every instance of nil in your code with '() and you'd have the same code. And before you get flashbacks of Javascript and null, just remember unlike JS, there's only a single falsey value and that is the empty list, and there is no automatic type conversion that has surprising boolean semantics. Ultimately CL manages to turn nil punning into a very nice feature that makes the spaghetti code you're talking about unnessary while also avoiding many of the 'null'-y issues of other languages due to 1) nil is a value 2) there is only one falsey value and that is nil 3) no automatic type conversion