Translating Lists From Common Lisp to Clojure
Translating Lists From Common Lisp to Clojure
While lists are common in Clojure, they aren't as central as they are in Common Lisp. Translating list-oriented concepts from Common Lisp to Clojure needs some finesse.
Join the DZone community and get the full member experience.Join For Free
The CMS developers love. Open Source, API-first and Enterprise-grade. Try BloomReach CMS for free.
The list is a very central data structure in Common Lisp, especially when looking at educational content (such as classic books). In Clojure, lists exist and are used quite a bit, but their place is less central, considering their inherent inefficiency compared to other data structures is acknowledged. This often leaves us with a need to translate a lot of list-oriented idioms from Common Lisp to equivalent Clojure code.
When working with lists, Common Lisp often uses the predicates consp and listp; it turns out that neither of these has a direct equivalent in Clojure, and we must think carefully when translating code using consp or listp.
Throughout this post, I assume cons cells are only used to construct proper lists, such as (cons 'a (cons 'b nil)). Other uses of cons in Common Lisp are fairly rare; this fact is acknowledged by the language specification. For example, listp returns true on a non-list cons cell, since it's assumed that cons cells are used mostly for lists:
> (cons (cons 1 2) 3) ((1 . 2) . 3) > (listp (cons (cons 1 2) 3)) T
The consp predicate simply checks if its argument is a cons cell:
> (consp (cons (cons 1 2) 3)) T > (consp (cons 1 (cons 2 nil))) ;; list using cons T > (consp '(1 2)) ;; nicer way make the same list T
It will reject anything that's not a cons cell, most notably symbols and numbers:
> (consp 1) NIL > (consp 'foo) NIL
An unfortunate side effect is that consp also rejects nil; but nil is used to mean an "empty list" in Common Lisp (it's equivalent to '()), so consp is not a convenient function to use to check whether something "is a list". That's what listp is for. Listp is simply an or between the consp and null predicates:
> (consp nil) NIL > (listp nil) T > (consp '()) NIL > (listp '()) T
As mentioned above, listp simplistically accepts non-list cons cells as lists as well, but that's rarely a problem in practice.
Now, if you're reading some book or article that uses Common Lisp as its demonstration language, there's a good chance calls to consp and listp are nonchalantly scattered all over the code; the difference between these two, as we've just seen, is quite subtle, and authors use one or the other based on the nature of the problem faced. For example, if you see a consp, it's most likely there because the author wants to explicitly reject empty lists.
In Clojure, our first attempt would be to use seq. It returns a truthy value for sequences, and can be used within conditions:
cl-in-clj.core=> (if (seq (cons 1 (cons 2 nil))) 'yes 'no) yes cl-in-clj.core=> (if (seq '(1 2)) 'yes 'no) yes
This appears to be a reasonable replacement for consp, until we try it with a symbol:
cl-in-clj.core=> (if (seq 'foo) 'yes 'no) IllegalArgumentException Don't know how to create ISeq from: [...]
Oops. That's not good. We need a better way to detect lists that won't blow up on us. An alternative is
cl-in-clj.core=> (list? '(1 2)) true cl-in-clj.core=> (list? 'foo) false cl-in-clj.core=> (list? '()) true
But wait, this returns true for '(); so it's more like an equivalent for listp than for consp. For consp, we'd need to make sure we return false for an empty list:
cl-in-clj.core=> (defn my-consp [obj] (and (list? obj) (not (empty? obj)))) #'cl-in-clj.core/my-consp cl-in-clj.core=> (my-consp '(1 2)) true cl-in-clj.core=> (my-consp 'foo) false cl-in-clj.core=> (my-consp '()) false
Note that I'm using (not (empty? ...)) rather than the more idiomatic (seq ...); this is on purpose. If the object is a sequence, seq will return it and that will be the value of the and. Sometimes, I really only want a boolean, so (not (empty? ...)) is more accurate.
There's another issue here, having to do with nil. In Common Lisp, nil is just the empty list. In Clojure, they're different. The list? defined above will return false for nil, unlike its Common Lisp counterpart. The real solution to this in Clojure is to avoid using nil to mean the empty list; instead just use the empty list '(). This means we have to be careful when calling first on an empty list, since it returns nil.
While on the topic of nil, Common Lisp's null is true on the empty list while Clojure's (nil? '()) is false. To approximate the behavior of null, we could do:
cl-in-clj.core=> (defn my-null [obj] (or (nil? obj) (empty? obj))) #'cl-in-clj.core/my-null cl-in-clj.core=> (my-null nil) true cl-in-clj.core=> (my-null '()) true
To conclude, if you need Common Lisp's listp, use Clojure's list? but be aware of nils. It's best to avoid nils in Clojure list processing, but this isn't always trivial since (first '()) returns nil.
If you need consp, you'll have to define something like my-consp, unless you are OK with an exception being thrown on symbols and numbers, in which case seq should do.
Published at DZone with permission of Eli Bendersky . See the original article here.
Opinions expressed by DZone contributors are their own.