Disclamer: I'm a Clojure noob, so bad code follows...
I have been putzing around with implementing a Simple Temporal Network
(a graph structure for scheduling problems) in Clojure. In the
process, I wanted to generate records that had default values.
I ran into this blog post by cemerick (http://cemerick.com/2010/08/02/
defrecord-slot-defaults/), but it doesn't do exactly what I needed.
I wanted a macro where I could optionally define defaults (rather than
forcing me to define defaults), and I wanted the 'constructor' to
accept key-value pairs that would be used instead of the defaults when
given.
So I toyed around with the code and came up with the following:
-----------------------------------------------------------------------------------------------------------------------------
(defn ensure-key-params
"Makes sure the arguments are keys - (:a 1 :b 2)"
[key-vals]
(->> key-vals
(map (fn [[key val]]
[(keyword key) val]))
(apply concat)))
(defmacro make-instance
"Creates an instance of a record based on the passed arguments and
the default arguments"
[cname fields user-vals default-vals]
`(let [user-map# (apply hash-map ~user-vals)
default-map# (apply hash-map ~default-vals)
record-vals# (list*
(for [rawkey# '~fields]
(let [key# (keyword rawkey#)]
(get user-map# key# (default-map# key#)))))]
(eval (conj record-vals# ~cname 'new))))
(defmacro defrecord+
"Defines a new record, along with a make-RecordName factory function
that
returns an instance of the record initialized with the default
values
provided as part of the record's slot declarations. e.g.
(defrecord+ foo
[(a 5) b c])
(make-foo :b 4)
=> #user.foo{:a 5, :b 4, :c nil}"
[name slots & etc]
(let [slots+ (for [slot slots]
(if (list? slot)
slot
(list slot nil)))
fields (->> slots+ (map first) vec)
default-vals (ensure-key-params slots+)]
`(do
;;Create the record with the given name and fields
(defrecord ~name
~fields
~...@etc)
;;Define the constructor macro
(defmacro ~(symbol (str "make-" name))
~(str "A factory function returning a new instance of " name
" initialized with the defaults specified in the corresponding
defrecord+ form.")
[& user-vals#]
(let [name# ~name
fields# '~fields
default-vals# '~default-vals]
`(make-instance ~name# ~fields# '~user-vals# '~default-vals#)))
~name)))
-----------------------------------------------------------------------------------------------------------------------------
So now I can do this:
(defrecord+ foobar
[(a (ref {})) b (c 5)])
and
(make-foobar :c "foobar")
and get
#:user.foobar{:a #<r...@79d0569b: {}>, :b nil, :c "foobar"}
So it works as intended, but it seems to me that the code could be
made cleaner. I'd appreciate suggestions for improvements.
Thanks much,
Anthony
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to [email protected]
Note that posts from new members are moderated - please be patient with your
first post.
To unsubscribe from this group, send email to
[email protected]
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en