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

Reply via email to