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

Reply via email to