CLJ-COLL: Bringing Clojure's Immutable Collections to Common Lisp
Share this article
Unifying Lisp Dialects: CLJ-COLL's Immutable Revolution
For developers navigating both Common Lisp and Clojure ecosystems, a persistent friction point has been the divergent approaches to collections. Clojure's immutable, persistent data structures enable elegant functional programming patterns but remain foreign to Common Lisp's mutable-by-default world. Enter CLJ-COLL – a new library that implements Clojure's collection, sequence, and lazy-seq APIs directly in Common Lisp.
Why This Matters for Polyglot Developers
CLJ-COLL isn't a Clojure interpreter. Instead, it provides targeted interoperability:
;; Create immutable Clojure-style collections in CL
(let ((vec [1 2 3])
(map {:a 10 :b 20}))
(conj vec 4) ; Returns new persistent vector [1 2 3 4]
(assoc map :c 30)) ; Returns new map {:a 10 :b 20 :c 30}
Key capabilities include:
- Full Clojure collection API (257+ functions from clojure.core)
- Seamless interoperability with Common Lisp lists, vectors, and hash-tables
- Lazy sequences with Clojure-style map, filter, and transducer support
- Optional reader syntax for {:maps}, #{sets}, and [vectors]
- Mutable alternatives via 'M functions' (mfilter, mmap) returning CL-native types
Under the Hood: FSet-Powered Immutability
CLJ-COLL leverages FSet's battle-tested immutable collections under the hood while maintaining Clojure's API contracts. This provides:
;; Sequence operations work across CL and immutable types
(filter #'odd? (list 1 2 3 4)) ; CL list → (1 3)
(take 2 (lazy-seq [1 2 3 4])) ; Immutable vector → (1 2)
Notably, the library handles conversion headaches:
- Equivalence semantics via equal? (not Clojure's =) for cross-type comparisons
- Intelligent printing configures CL hash-tables to print as {:cl-style maps}
- Reader macros allow Clojure-like literals through named-readtables
Critical Considerations and Limitations
While CLJ-COLL significantly narrows the gap, developers should note:
"Equality semantics diverge for mutable collections. FSet maps use
fset:equal?for keys, while CL hash-tables default tocl:equal. This affects functions likegetandcontains?when mixing mutable/immutable types."
Other constraints:
- No chunking in lazy sequences (performance tradeoff)
- Thread safety not guaranteed for stateful transducers
- Sorted collections (sorted-map, sorted-set) not yet implemented
- Java interop naturally absent from the CL environment
The Polyglot Future
CLJ-COLL represents more than convenience—it signals growing recognition that Lisp dialects can share innovations while preserving their identities. For teams maintaining Clojure and CL codebases, it reduces cognitive switching costs. For library authors, it enables sharing collection-manipulation patterns across ecosystems.
The project explicitly avoids reimplementing Clojure's syntax or special forms, focusing instead on the collection abstractions that make Clojure's data-oriented programming so expressive. As maintainer David Tenny notes, this approach lets developers "leverage decades of Common Lisp tooling while adopting Clojure's functional collection paradigms where beneficial."
Source: CLJ-COLL GitHub Repository