4

While learning clojure, I was very surprised to find out that these two objects are different types:

(list? `(inc))   ;; true
(list? `(inc 1)) ;; false

In theory, I understand why the second form returns false, that object is actually a clojure.lang.Cons. In practice, though, I don't understand why that is happening.

Why does the reader read `(inc) different from `(inc 1)? What is happening under the hood?

user2864740
  • 60,010
  • 15
  • 145
  • 220
Malabarba
  • 4,473
  • 2
  • 30
  • 49
  • 1
    It doesn't relate to inc, compare `(list? \`(a))` with `(list? \`(a b))`. Anyway, `(type ..)` reveals the first is a PersistentList, the second is a Cons. But the why .. no idea. – user2864740 Mar 18 '15 at 20:24
  • @CharlesDuffy Well, in the other lisps I know when the cons' second value is `nil` it is a one-element list (i.e. `(inc)`). Is that different in clojure? – Malabarba Mar 18 '15 at 20:35
  • @CharlesDuffy `(type \`(a b c))` also results in Cons. It is only the `\`(singular)` form that appears different and is read as a PersistentList. – user2864740 Mar 18 '15 at 20:50
  • @user2864740, indeed; I just learned something there. – Charles Duffy Mar 18 '15 at 20:52
  • `(list? '(a b))` returns `true`. I'm assuming that the syntax quote doesn't return a list because it's easier to implement `~@` splicing with general seqs (with `concat` or something). It would probably make more sense to check for `seq?` instead of `list?`. – DaoWen Mar 18 '15 at 20:58
  • @DaoWen Possibly. I need a predicate to match quoted lists or code, but not any other collection. Is that `seq?`? I know it checks for `ISeq`, but I don't whether any other collections implement `ISeq`. (in any case, that's a side topic, I'm still curious about the question) – Malabarba Mar 18 '15 at 21:47
  • This is interesting: https://clojuredocs.org/clojure.core/cons#example-54f820aee4b0b716de7a6530. Anyway, you should probably just roll out your own predicate; e.g.: `(defn code? [x] (or (list? x) (instance? clojure.lang.Cons x)))` – DaoWen Mar 18 '15 at 22:17
  • @DaoWen Yes, [that's what I'm doing](https://github.com/Bruce-Connor/cider-nrepl/blob/debug/src/cider/nrepl/middleware/util/instrument.clj#L38), but the question was bugging me :-). And that link actually helps quite a bit. – Malabarba Mar 18 '15 at 23:23
  • According to http://insideclojure.org/2015/01/02/sequences/ `clojure.lang.PersistentList` is the only collection that implements `ISeq` and will be true for `seq?`. (At bottom, starting just above "Predicates and functions.") – Shannon Severance Mar 19 '15 at 00:32
  • @ShannonSeverance That's odd. I'm getting that ```(seq? `(x y))``` is true and ```(list? `(x y))``` is false. Which I _think_ contradicts that. – Malabarba Mar 19 '15 at 01:19
  • I don't think `clojure.lang.Cons` are considered collections, and it does implement `ISeq`. – Shannon Severance Mar 19 '15 at 01:21
  • @ShannonSeverance But it returns true on `coll?` :-\ – Malabarba Mar 19 '15 at 01:23
  • Apparently I was wrong. Effing Clojure. – Shannon Severance Mar 19 '15 at 01:25
  • from that link "seq? - checks whether an instance implements ISeq" – noisesmith Mar 19 '15 at 01:45

2 Answers2

2

list? is actually a function of very limited usefulness. In fact I have yet to see Clojure code that used list? without it being at best a poor choice, more often the cause of a bug.

If you want to know if something is "listy", seq? is a great choice.

in action:

user=> (pprint/print-table (for [item [[] () `(a) `(a b) (seq [1])]]
                              {'item (pr-str item)
                               'seq? (seq? item)
                               'list? (list? item)
                               'type (type item)}))
|            item |  seq? | list? |                                           type |
|-----------------+-------+-------+------------------------------------------------|
|              [] | false | false |            class clojure.lang.PersistentVector |
|              () |  true |  true |    class clojure.lang.PersistentList$EmptyList |
|        (user/a) |  true |  true |              class clojure.lang.PersistentList |
| (user/a user/b) |  true | false |                        class clojure.lang.Cons |
|             (1) |  true | false | class clojure.lang.PersistentVector$ChunkedSeq |
noisesmith
  • 20,076
  • 2
  • 41
  • 49
  • The trouble is that vectors are schizophrenic: They say they are associative, but they provide a sequence of values, not of key-value pairs, under `seq`. – Thumbnail Mar 19 '15 at 12:29
  • They also say that they are `seqential?` though. – noisesmith Mar 19 '15 at 14:18
  • I see that `clojure.lang.Sequential` is empty - so types simply elect to implement it. Nevertheless, is `sequential?` better than `seq?` here, as an improvement upon `list?`? – Thumbnail Mar 19 '15 at 14:38
  • I think the asker would have to answer that. I assumed (perhaps wrongly) that they wanted "listy" things, eg. things that would print with () and conj to the front. – noisesmith Mar 19 '15 at 15:13
2

When the reader encounters a syntax-quoted form, that turns out to be a collection, it will iterate over each element and call syntax-quote recursively. The result is consed, beginning with nil.

So it comes down to the question why the following holds:

> (list? (cons 'inc nil))
true
> (list? (cons 'inc (cons 1 nil)))
false

This seems to be a matter of definition.

Jens
  • 150
  • 8