Hi,

first of all we should start with the form we finally want to have:

(defrecord Foo [a b c])

(defn make-Foo
  [& {:keys [a b c] :or {a :x c :z}}]
  (Foo. a b c))

; Use as: (make-Foo :b :f) => (Foo. :x :f :z)

The only annoying part is the boilerplate of defining make-Foo.
What we would like to write is:

(defrecord+ Foo [[a :x] b [c :z]])

So how do we get from the lower form to the upper form? Basically
you have that already, but you should not make the constructor
a macro, but a function.

General note: You want to use almost always vectors instead of lists
to group things. Vectors are the Right Tool here. Lists are much less
in important in Clojure than in other Lisp-like languages. Besides
their
role in code representation. (Note: a seq is not a list)

Then we have to define the constructor function. Here we exploit
the "old" new operator to save us from modifying the type symbol.
Well, and that's basically it. There is no need for eval and related
dark magic.

(defmacro defrecord+
  [record-name fields-and-values & record-body]
  (let [fields-and-values (map #(if (vector? %) % [% nil]) fields-and-
values)
        fields            (vec (map first fields-and-values))
        default-map       (into {} fields-and-values)]
    `(do
       (defrecord ~record-name
         ~fields
         ~...@record-body)
       (defn ~(symbol (str "make-" (name record-name)))
         [& {:keys ~fields :or ~default-map}]
         (new ~record-name ~...@fields)))))

And the result:

user=> (defrecord+ Foo [[a :x] b [c :z]])
#'user/make-Foo
user=> (make-Foo :b :f)
#:user.Foo{:a :x, :b :f, :c :z}
user=> (macroexpand-1 '(defrecord+ Foo [[a :x] b [c :z]]))
(do
  (clojure.core/defrecord Foo [a b c])
  (clojure.core/defn make-Foo
    [& {:or {a :x, b nil, c :z}, :keys [a b c]}]
    (new Foo a b c)))

(macroexpand output formatted for readability)

This can be easily extended, that the constructor also allows
arbitrary
other keywords, besides the usual defined fields. This is left to the
astute reader as an excerise. ;) Hint: there is also :as in
destructuring.

Bottom line: Avoid macros at all cost! If a function does the job, use
a function! They are easier to write, can be passed around, can be
apply'd, ...

Hope this helps.

Sincerely
Meikel

-- 
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