Hacker News new | past | comments | ask | show | jobs | submit login

I'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.



This 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.


Optional<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.


  (get m :foo :not-found)
Problem solved.


I 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.


Now you're getting at a much more fundamental issue. The issue here is that m is not a product type but an open map that allows for an arbitrary collection of keys. No static typing system will solve this either.

If you're really that worried about typos and don't need an open map, Clojure has a solution as well in the form of defrecord and deftype. Now the compiler will catch the problem.


Yes, I don't really think any language solves this problem, just moves it around.

It's just an issue in general whenever the happy path includes optionality as a possible result, how do you then distinguish the happy path of getting a missing value, which you expect is a possibility given the use case, versus a bug to have set the value or have been provided the value or have queried for the value?

I think the only languages that reduce the scope of this problem are those where you can define non-nullable types. In that at least when you know for a fact your use case shouldn't have "missing" things, the compiler can catch those bugs. You can also handle this in Clojure with runtime assertions, albeit catching the bug a little later in development.

But for the times when optional is a real possibility, you still have this issue.

If you move to an explicit set of valid keys, it could be that something failed to assign them a value, etc.

Edit: I guess maybe something where the language simply has no null ever could work. Like say a field not yet assigned a value would have the value :unset, and so if someone doesn't provide a value for the field, you'd still set it, but with the new value :missing. Now technically you could distinguish a bug as `:unset` versus actually missing as `:missing`. I'm not sure this would work for dynamic use cases though, where like you don't know the full range of possibilities in advance. But if you do, you could check that nothing is `:unset` and if it is, you know there is a bug, otherwise it should be `:missing` or some other value.

Edit2: Ok, now that I played a bit more with what you said, I realized thay your solution is actually better then I thought, but it depends how you model things.

    (get {:foo :missing} :fooo :unset)
    ;> :unset

    (get {:foo :missing} :foo :unset)
    :> :missing
If you model optionality as an explicit `:missing` value on a set key, like assoc the key `:foo` with value `:missing` to indicate that no value was provided for it. With the 3rd argument to `get`, you can now tell the difference between a bug or an actually missing value.

It does seem to require a bit of care, and I don't know if its just.moving the issue somewhere else, but it could be an approach.


Agreed. The problem often presents itself downstream, or in a totally different place from where it originated.




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: