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