On Thu, Jan 27, 2011 at 9:34 AM, Lee Spector <lspec...@hampshire.edu> wrote: > > There was a recent thread on this. Some of the issues that were raised (and > for which workarounds were presented) were slot defaults, keyword args to > struct-map, and the fact that defstructs implement IFn. I had also found it > more elegant to write a macro or two to expand into struct-related code than > into record-related code. I do think that decent workarounds were presented > for all of the issues that were raised, but they were workarounds and the > built-in features of records don't yet include the struct features that were > raised.
Fairly easy: (def record-data (atom {})) (defmacro defrecord-x [name fields & more] (let [fieldnames (vec (take-nth 2 fields)) defaults (vec (take-nth 2 (rest fields)))] `(let [r# (defrecord ~name ~fieldnames ~'clojure.lang.IFn (invoke [this# k#] (get this# k#)) (invoke [this# k# n#] (get this# k# n#)) ~@more)] (swap! record-data update-in [r#] assoc :defaults ~defaults) r#))) (defn record [rec & args] (let [d (:defaults (@record-data rec)) n (count args) a (concat args (drop n d))] (eval (apply list (quote new) rec a)))) user=> com.example.sbox=> (defrecord-x Foo [a 1 b 2]) user.Foo user=> (record Foo 3 4) {:a 3, :b 4} user=> (record Foo 3) {:a 3, :b 2} user=> (record Foo) {:a 1, :b 2} user=> ((record Foo 3) :b) 2 user=> ((record Foo 3) :c) nil user=> ((record Foo 3) :c 42) 42 The record function is a bit icky under the hood; the only apparent way to "apply" variable arguments to a record constructor is to use eval. But with that, record behaves basically the way struct does. It can be used with a normal defrecord, with the correct number of arguments (unlike struct it won't default the remaining fields to nil with too few arguments, but instead will throw), and it can be used with a defrecord-x record as shown above. The defrecord-x macro acts like defrecord, except it also stores defaults where record will look for them, and the record implements IFn. The field name vector in defrecord becomes a bindings-like vector in defrecord-x (no destructuring, sorry). Calling the record with the wrong number of arguments throws AbstractMethodError rather than IllegalArgumentException; changing that is a simple but tedious matter of adding a lot of (invoke [this# _# _# _# ...] (throw ...)) lines to defrecord-x. Because of the way that defrecord works, you can still create records with a custom IFn implementation with defrecord-x. If the same interface is given two implementations in defrecord, the second one simply supersedes the first. Since the ~@more is at the end of the defrecord form in the macro, a user-supplied IFn implementation will supersede the implementation defrecord-x provides. Adding a keyfn option to the record function is left as an exercise for the reader. :) -- You received this message because you are subscribed to the Google Groups "Clojure" group. To post to this group, send email to clojure@googlegroups.com Note that posts from new members are moderated - please be patient with your first post. To unsubscribe from this group, send email to clojure+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/clojure?hl=en