Skip to content

Instantly share code, notes, and snippets.

@Solaxun
Last active July 3, 2020 19:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Solaxun/2e010f8e14c87456f243c0b972e69d1f to your computer and use it in GitHub Desktop.
Save Solaxun/2e010f8e14c87456f243c0b972e69d1f to your computer and use it in GitHub Desktop.
Clojure Cons Cell
(def NIL (symbol "NIL"))
(deftype ConsCell [CAR CDR]
clojure.lang.ISeq
(cons [this x] (ConsCell. x this))
(first [this] (.CAR this))
;; next and more must return ISeq:
;; https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/ISeq.java
(more [this] (if
(= (.CDR this) NIL)
clojure.lang.PersistentList/EMPTY
(ConsCell. CDR NIL)))
(next [this] (if
(= (.CDR this) NIL)
nil ;; next returns nil when empty
(ConsCell. CDR NIL)))
clojure.lang.Seqable
(seq [this] this)
;; for some reason this marker protocol is needed otherwise compiler complains
;; that `nth not supported on ConsCell`
clojure.lang.Sequential
clojure.lang.IPersistentCollection
(count [this] (if
(= (.CDR this) NIL)
0
(inc (count (.CDR this)))))
(empty [this] false)
(equiv [this other] false))
(ConsCell. 5 10)
(rest (ConsCell. 5 10))
(rest (rest (ConsCell. 5 10)))
(next (ConsCell. 5 10))
(next (next (ConsCell. 5 10)))
;; I somewhat beat this into submission without fully understanding why certain things are
;; required or work the way they do. I basically poked around for what protocols could potentially
;; be tied to a requirement to have `nth` and found sequential to be a possibility, so put that
;; marker protocol in and it worked. No idea why it's required other than all seqs are assumed to be
;; sequential, so maybe they must be implemented together?
;; Also, I know from experience that next should return nil vs an empty list (like rest does), so I made
;; that change, but an unanticipated benefit was that it fixed an issue I was getting where returning the
;; empty list (like in `more`), actually shows up in the Cons representation as the last item:
;; For example, defining next as so:
;; (next [this] (if
;; (= (.CDR this) NIL)
;; clojure.lang.PersistentList/EMPTY
;; (ConsCell. CDR NIL)))
;; gives you the following when you create a Cons:
;; (.ConsCell 10 20)
;; >> (5 10 nil)
;; If you do it like this:
;; (next [this] (if
;; (= (.CDR this) NIL)
;; nil ;; next returns nil when empty
;; (ConsCell. CDR NIL)))
;; You get the expected:
;; (.ConsCell 10 20)
;; >> (5 10)
;; I assume the prior version is returning an empty list which gets converted to a seq at some point
;; and therefore shows up in the representation, but I'm fuzzy on the details. Playing with deftypes
;; is outside my comfort zone.
@simon-brooke
Copy link

That's 99% there. I found when I replaced (ConsCell. CDR NIL) with (.CDR this) in both more and next, it worked - see my fork of your gist.

Now I'm getting:

beowulf.core=> (use 'beowulf.cons-cell :reload)
nil
beowulf.core=> (use 'beowulf.read :reload)
nil
beowulf.core=> (use 'beowulf.print :reload)
nil
beowulf.core=> (map str (generate (simplify (parse "(A B C)"))))
("A" "B" "C")

Which is good enough for me!

@Solaxun
Copy link
Author

Solaxun commented Aug 14, 2019

Hmmm that's odd, I must be missing something - I was getting "long cannot be cast to ISeq" without wrapping the result in another Cons (which is an ISeq), that's one of the changes I had to make to get this to run. I assumed from reading the java file linked in the comments that this was required since the protocol signatures indicate more and next must return an ISeq.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment