Why Clojure idiom prefer to return nil instead of

2019-02-08 16:24发布

问题:

From a comment on another question, someone is saying that Clojure idiom prefers to return nil rather than an empty list like in Scheme. Why is that?

Like,

(when (seq lat) ...)

instead of

  (if (empty? lat)  
    '() ...)

回答1:

I can think of a few reasons:

  • Logical distinction. In Clojure nil means nothing / absence of value. Whereas '() "the empty list is a value - it just happens to be a value that is an empty list. It's quite often conceptually and logically useful to distinguish between the two.

  • Fit with JVM - the JVM object model supports null references. And quite a lot of Java APIs return null to mean "nothing" or "value not found". So to ensure easy JVM interoperability, it makes sense for Clojure to use nil in a similar way.

  • Laziness - the logic here is quite complicated, but my understanding is that using nil for "no list" works better with Clojure's lazy sequences. As Clojure is a lazy functional programming language by default, it makes sense for this usage to be standard. See http://clojure.org/lazy for some extra explanation.

  • "Falsiness" - It's convenient to use nil to mean "nothing" and also to mean "false" when writing conditional code that examines collections - so you can write code like (if (some-map :some-key) ....) to test if a hashmap contains a value for a given key.

  • Performance - It's more efficient to test for nil than to examine a list to see if it empty... hence adopting this idiom as standard can lead to higher performance idiomatic code

Note that there are still some functions in Clojure that do return an empty list. An example is rest:

(rest [1])
=> ()

This question on rest vs. next goes into some detail of why this is.....



回答2:

Also note that the union of collection types and nil form a monoid, with concatenation the monoid plus and nil the monoid zero. So nil keeps the empty list semantics under concatenation while also representing a false or "missing" value.

Python is another language where common monoid identities represent false values: 0, empty list, empty tuple.



回答3:

From The Joy of Clojure

Because empty collections act like true in Boolean contexts, you need an idiom for testing whether there's anything in a collection to process. Thankfully, Clojure provides such a technique:

(seq [1 2 3])
;=> (1 2 3)

(seq [])
;=> nil

In other Lisps, like Common Lisp, the empty list is used to mean nil. This is known as nil punning and is only viable when the empty list is falsey. Returning nil here is clojure's way of reintroducing nil punning.



回答4:

Since I wrote the comment I will write a answer. (The answer of skuro provides all information but maybe a too much)

  1. First of all I think that more importend things should be in first.
  2. seq is just what everybody uses most of the time but empty? is fine to its just (not (seq lat))
  3. In Clojure '() is true, so normaly you want to return something thats false if the sequence is finished.
  4. if you have only one importend branch in your if an the other returnes false/'() or something like that why should you write down that branch. when has only one branch this is spezially good if you want to have sideeffects. You don't have to use do.

See this example:

(if false '() (do (println 1) (println 2) (println 3)))

you can write

(when true (println 1) (println 2) (println 3))

Not that diffrent but i think its better to read.


P.S.

Not that there are functions called if-not and when-not they are often better then (if (not true) ...)