Null evil vs nil punning

A new discussion has sprung up on reddit1 on the well-established topic of the evils of Null (billion dollar mistake and all that). My question is, to what degree does this apply to lisp in general, which might embrace nil punning and other conventions? In lisp is nil different than null for this discussion? And then, in Clojure, which is hosted on Null-having languages like Java and JavaScript, how does this argument shape up? Is having nil any different from having null, with all the costs involved?

Footnotes

1 reddit here: https://www.reddit.com/r/ProgrammingLanguages/comments/w6fdpv/nulls_really_do_infect_everything_dont_they/

I think it is a question of types. Null is evil if every value can be null without the type system (dynamic or static) complaining about it. In Java, you can say String foo = null;, even though null is (obviously?) not a String. This »anything can be Null« is the infamous »million dollar mistake«, in my view. Lisps in general don’t do this. If you type hint resp. declare something as String and pass a NIL to it, it will complain at compile time or at run time at exactly that point, not three systems later.

NIL punning works the other way around: if NIL has a useful meaning that you incorporate into your data structure, then it is exactly defined into it: NIL is false, NIL is the empty list. (This derives from formal logic, where »no solution« is Bottom (⊥), which is false. The concept of generalized booleans, i. e. »everything has a boolean value«, is quite convenient and unambiguous and this only becomes awkward when you try to interop with languages (be it a host programming language like Java or a data language like JSON) that differentiate between these.)

This makes no problem because this NIL is never unexpected. It’s not that you try to add something to an Integer but, surprise!, it actually isn’t. Or ask »is this true?« and BÄM! It’s not even a Boolean you fool! Or iterate over a list and Oh Noes! it’s not even a list!

5 Likes

Interesting timing to see a reply here this morning as this subject cropped up for me late last night in a discussion on the Java Moose forums about refactoring. The (Java) code being refactored had a condition in it to either produce null or a String as the result of a parsing function. In this case, they’d used String.indexOf() which returns -1 for no match, and had a condition to specifically translate that to null.

I decided to show the Clojure equivalent where both clojure.string/index-of and re-find return nil if there’s no match and so you no longer need to special case for the null return.

I used (second (re-find ..)) with a group in the regex to replace the entire function they were asking about refactoring, eliminating all the statements in their function, leaving just one expression without if – part of the refactoring goal in the thread was to eliminate the conditional (in this case it was specifically the ternary operator in a return statement. Using (and (str/index-of ..) (subs .. (str/index-of ..) ..)) would also work because of nil-punning.

Dropping Clojure into a Java thread wasn’t welcomed by everyone but someone did say that if Clojure addresses the Billion Dollar Mistake, it was definitely worth a look!

7 Likes

Dropping Clojure into a Java thread wasn’t welcomed by everyone but someone did say that if Clojure addresses the Billion Dollar Mistake, it was definitely worth a look!

Any chance you can share the code? :slight_smile: Java version with -1 failure value, and the Clojure alternative.

The thread is public:

Enjoy!

2 Likes

This might derail the thread somewhat, but I think it also shows that languages alone are not the point.

I would actually parse that string as soon as it enters my domain. I want to get a {:sheet "boo", :start "a1", :end "b2"}. This sheet language is not too small to make it right. What if the syntax gets extended later to also allow other files or other computers (whose names might contain \! and \:)?

In other words, the method that started that thread shouldn’t exist in the first place.

I don’t think the code by that book author is much better, because it still has the method, it just makes the code much more complicated without touching the core issue (and claiming “it’s generated, so no rules” is another issue or two).

In Java, I would have an (immutable) SheetRef class which has a constructor or static method that parses from a string (among others possibly). In keeping with Java style, I would probably then define getters for the parts. I would definitely not do partial parses all over the code.

Now, to get back to our thread, is the NIL returned by :sheet good? I think yes, but maybe you want to think about your domain, whether you should put in the current sheet name upon parsing if absent?

4 Likes

I agree with all your points – and found the whole thread somewhat perplexing in terms of actually solving a problem and that both the book author and one other person turned a five-line function into 40-60 lines of code to “do the same thing” when two lines of Clojure was sufficient and relatively readable.

Expanding the regex to match the sheet name and the cell references would be straightforward and wrapping it in zipmap to create the :sheet, :start, :end map would barely add any overhead.

1 Like

I think that thread highlights why people avoid java these days. The thinking that leads to their solution, like Sean said turning 5 lines into 40+, is what’s wrong with java codebases.

Programming is algorithms and data structures. The problem is they tried to solve a data structures problem with an algorithm. Representing a sheet as a string is simply the wrong data structure. Use a better data structure and everything else falls into place.

The main difference is that null can replace anything. That leads to a false sense of security in a statically typed language. It’s the same problem typescript has with the any type, which makes the entire language not type safe. You can’t trust the types in TS because if anything happens to pass through any, all bets are off. Java is actually the same. It’s not fully type safe.

Clojure, like all dynamic languages, makes no guarantees about type safety. Since a value could be anything, it could also be nothing. That’s the only guarantee the language gives you. There is no false sense of security because there’s no sense of security to begin with.

My 2 cents: The real problem in Java is that most things you would want to do with it throw an exception. Null doesn’t allow methods.

In Clojure nil has a type: (type nil) ;=> nil

And it has reasonable behavior with many of the built-in functions. There are still many functions with questionable behavior. But you always expect nil so it becomes part of your thinking. In other words, you learn what is and isn’t safe to do with a nil.

4 Likes

Have you seen the 2018 video where Rich talks about this?

Maybe Not - Rich Hickey

Not sure who that question was aimed at… but many of us saw that talk live and it sat well with the views we already have, for the most part.

Good reminder! I need to rewatch that Hickey one.

By the way, has this spec/select thing been implemented and published somewhere?

There’s a Spec 2 repo on GH but work on it has stopped for the time being, according to Alex (when the question came up on Slack the other day).