Skip to content

Instantly share code, notes, and snippets.

@cemerick
Created September 19, 2012 15:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save cemerick/3750288 to your computer and use it in GitHub Desktop.
Save cemerick/3750288 to your computer and use it in GitHub Desktop.
Extending defrecord types in Clojure
;; Records are just types that provide default implementations of certain
;; key interfaces that allow them to stand in for maps.
;; This set of interfaces/protocols is not closed though; you can certainly make them
;; useful in places where maps aren't, e.g. w/ sequential destructuring:
=> (defrecord Point [x y]
clojure.lang.Indexed
(nth [_ i] (case i 0 x 1 y
(throw (IndexOutOfBoundsException.))))
(nth [_ i default]
(case i 0 x 1 y
default)))
user.Point
=> (let [[a b] (Point. 1 2)]
(+ a b))
3
;; (Further examples can be found in http://clojurebook.com ;-)
;; The macro to generalize the above for records that define any number of slots
;; is fairly simple, and left as an exercise.
@sjl
Copy link

sjl commented Sep 19, 2012

Yeah, I figured there was a protocol you could extend and tried to find it by digging through the Clojure source a bit but couldn't uncover it. Apparently clojure.lang.Indexed was what I was looking for. Is there a listing of the built-in protocols anywhere?

And I wish this were part of the language itself. Clojure may not implement records as sequential but to the user they are. Clojure gives you a (->Point x y) style constructor function that uses the ordering of the attrs you gave it (actually so does the Java-style Point. one).

@sjl
Copy link

sjl commented Sep 19, 2012

I think I've got a macro to automate this working based on your snippet
(defrec, rhymes with vec because it makes something you can index into):

(defmacro defrec [name args & body]
  (let [indexed-args (interleave (iterate inc 0) args)]
    `(defrecord ~name ~args
       clojure.lang.Indexed
       (~'nth [~'_ ~'i]
         (case ~'i
           ~@indexed-args
           (throw (IndexOutOfBoundsException.))))
       (~'nth [~'_ ~'i ~'default]
         (case ~'i
           ~@indexed-args
           ~'default))
       ~@body)))

(let [p (->Point 10 20)
      [x y] p]
  (println y x))
;=> 20 10

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