1. 10
  1.  

  2. 11

    Apologies to the author, but I find the comparisons to Haskell in this piece to be facile, and the practical examples of ways to solve the “nil problem” in Clojure to be glossing over the fact that nil remains a frustrating problem when nil is essentially a member of every single type (EDIT: although I should acknowledge that yes, the author does acknowledge this later in the piece–I just don’t find the rest compelling regardless). I don’t buy “nil-punning” just because it’s idiomatic (see the linked piece by Eric Normand); as far as I’m concerned talking about it as idiomatic is just putting lipstick on the pig. And fnil is a hack, the existence of which illuminates how Clojure didn’t get this right.

    That said, I’m not a lisp guy in my heart, so maybe I’m missing the subtle elegance of nil-punning somehow–but I’d love to see a Clojure without nil in it, and I think it’s telling that these articles always end up defensively comparing Clojure to Haskell.

    I believe that Clojure has some nice ad-hoc facilities for building composable abstractions, it’s got a lot of good stuff going for it and makes me want to shoot myself far less than any programming language I’ve used professionally to date. But as a user of the language I’ve found its type system, such as it is, to be incoherent, and I find the pervasiveness of nils to simply be a shitty part of the language that you have to suck up and deal with. Let’s not pretend that its way of handling nil is in any way comparable to Haskell (or other ML-family languages) other than being deficient.

    P.S. Per your first if-let example: when-let is a more concise way to express if-let where the else term is always going to evaluate to nil.

    1. 11

      Glad I’m not the only Clojurian who had this knee jerk reaction. nil is not Maybe t. Maybe t I can fmap over, and pattern match out of. With f -> Maybe t at least I know that it’s a function which returns Maybe t because the type tells me so explicitly and the compiler barks at me when I get it wrong. This gives certainty and structure.

      nil is the lurking uncertainty in the dark. The violation of the “predicates return booleans” contract. The creeping terror of the JVM. The unavoidable result of making everything an Object reference. I never know where nil could come from, or whether nil is the result of a masked type or key error somewhere or just missing data or… a thousand other cases. Forgot that vectors can be called as functions and passed it a keyword because your macro was wrong? Have a nil….

      Even nil punning doesn’t actually make that sense because it mashes a while bunch of datastructures into a single ambiguous bottom/empty element. Is it an empty map? set? list? finger tree? Who knows, it’s nil!

      Edit: wait wait but spec will save us! Well no… not unless you’re honest about the possibility space of your code. Which spec gives you no tools to infer or analyze.

      Have a nil.

      </rant>

      1. 4

        That said, I’m not a lisp guy in my heart, so maybe I’m missing the subtle elegance of nil-punning somehow

        Minor nitpick–please don’t lump all lisps in with nil-punners! Racket and Scheme have managed to avoid that particular unpleasantness, as has LFE.

        1. 3

          Not to mention Shen with a type system that subsumes Haskell’s…

          1. 3

            Even in Common Lisp, nil isn’t an inhabitant of every type. If you add (optional) type declarations saying that a function takes or returns an integer, array, function, etc., it isn’t valid to pass/return nil in those places. I think this is more of a Java-ism than a Lisp-ism in Clojure’s case.

            1. 1

              Isn’t nil indistinguishable from the empty list though, in Common Lisp?

            2. 2

              Yeah, just take it as further evidence that I’m not “a lisp guy” in that I conflated all lisps together here–sorry!

              1. 1

                In that case I’ll also forgive being wrong in the ps about if vs when.

                1. 1

                  Oh–would you explain further then? I always use when/when-let in the way I described (EDIT: and, I should add, many Clojure developers I’ve worked with as well assume this interpretation)–am I misunderstanding that semantically, fundamentally (from a historical lisp perspective or otherwise)?

                  1. 4

                    The ancient lisp traditions dictate that when you have two macros and one of them has an implicit progn (aka do) then that one should be used to indicate side-effects.

                    I know not all Clojure devs agree, but when you’re arguing that you should be less concerned than CL users about side-effects it’s probably time to reconsider your stance.

                    1. 1

                      Thanks!

                      EDIT: I want to add a question/comment too–I’m a bit confused by your mention of side-effects here, as in my suggestion to use when vs. if in these cases, I’m simply taking when at face value in the sense of it returning nil when the predicate term evaluates to false. I can see an argument that having an explicit else path with a nil return value may be helpful, but I guess my take is that…well, that’s what when (and the when-let variation) is for in terms of its semantics. So I guess I’m still missing the point a bit here.

                      …but nonetheless appreciate the historical note as I’m a reluctant lisper largely ignorant of most of this kind of ephemera, if you will forgive me perhaps rudely qualifying it as such.

                      1. 4

                        The typical Lisp convention is that you use when or unless only when the point is to execute some side-effecting code, not to return an expression, i.e. when means “if true, do-something” and unless means “if false, do-something”. This is partly because these two macros have implicit progn, meaning that you can include a sequence of statements in their body to execute, not just one expression, which only really makes sense in side-effectful code.

                        So while possible to use them with just one non-side-effecting expression that’s returned, with nil implicitly returned in the other case, it’s unidiomatic to use them for that purpose, since they serve a kind of signalling purpose that the code is doing something for the effect, not for the expression’s return value.

                        1. 3

                          Thanks mjn, that helps me understand technomancy’s point better.

                          Fully digressing now but: more generally I guess I’m not sure how to integrate these tidbits into my Clojure usage. I feel like I’ve ended up developing practices that are disconnected from any Lisp context, the community is a mix of folks who have more or less Lisp experience and the language itself is a mix of ML and Lisp and other influences.

                          In any case, thank you and technomancy for this digression, I definitely learned something.

            3. 3

              Oh I don’t think this solves the nil problem completely, but it’s as good as you can get in Clojure. If you’re coming from Java and have no or little experience in an ML-ish language, then this conceptual jump from null to Nothing is a bit difficult, so this article was primarily written for those people (beginner/intermediate Clojurists).

              Also you have to admit that, beyond Turing completeness, language features are an aesthetic. We can discuss the pros and cons of those features, but there is no One Right Way. A plurality of languages is a good thing - if there were a programming language monoculture we would have nothing to talk about at conferences and on internet forums :)

              1. 6

                Also you have to admit that, beyond Turing completeness, language features are an aesthetic. We can discuss the pros and cons of those features, but there is no One Right Way.

                It sounds to me like you are saying that all ideas about PL are of equal worth, which I disagree with. One can have a technical discussion about PL without it ultimately boiling down to “that’s just, like, your opinion man”

                1. 5

                  That’s not at all what I’m saying. It’s like in art, you can say that the Renaissance period is more important than the Impressionist period in art history, but that doesn’t mean Renaissance art is the “correct” way to do art. We can also have a technical discussion of artworks, but we weigh the strengths and weaknesses of the artworks against the elements and principles of art and how skillfully they are composed, we don’t say one artwork is correct and the other is incorrect. This is the basics of any aesthetic analysis.

                  Likewise in programming language design, we can discuss the technical merits of features, e.g. yeah Haskell has an awesome type system and I really enjoy writing Haskell, but that doesn’t mean Haskell is categorically better than Clojure. When you have a ton of legacy Java to integrate with (as I do), Clojure makes a lot of sense to use.

                  1. 3

                    PL criticism is done on utilitarian grounds, not aesthetic grounds. You acknowledge as much in your second paragraph when you give your reason for using Clojure. I guess you can look at PL as art if you want to but I don’t like that mode of analysis being conflated with what researchers in the field are doing.

                    1. 2

                      PL criticism is done on utilitarian grounds, not aesthetic grounds.

                      Why not both?

                      1. 1

                        When you’re trying to figure out which language is better, it’s not a question of aesthetics.

                        Though to be fair, “better” is really difficult to nail down, especially when people end up arguing from aesthetics.

            4. 6

              In ML (resp. Haskell), None (resp. Nothing) is a normal value that you can put inside Some (resp. Just). This is important if you need to define an abstract data type whose internal representation happens to be foo option (resp. Maybe Foo), but abstraction clients don’t need to know this fact.

              Alas, in Clojure, nilness can’t be abstracted away. So no, Nothing and nil are worlds away from each other, semantics-wise.

              1. 5

                For example doing this explicitly in Python is cumbersome:

                data = {"a": 1, "b": 2}
                
                if data["a"] is not None:
                    return data["a"]
                else:
                    return 0 # default return value    
                

                This is not how we do it in Python. if data["a"] is None is going to break if “a” is not in the dictionary, so instead we do:

                data = {"a": 1, "b": 2}
                return data.get("a", 0)
                

                … which returns the default if the key is absent and only does one look up.

                1. 4

                  I was going to point this out as well. Unfortunately even get with a default value can still return None. For example, {'a':None}.get('a',0) is None, not 0.

                  1. 2

                    Ah thanks, I haven’t done Python in a while. I’ll update the article.