views:

129

answers:

3

defrecord in clojure allows for defining simple data containers with custom fields.

e.g.

user=> (defrecord Book [author title ISBN])
user.Book

The minimal constructor that results takes only positional arguments with no additional functionality such as defaulting of fields, field validation etc.

user=> (Book. "J.R.R Tolkien" "The Lord of the Rings" 9780618517657)
#:user.Book{:author "J.R.R Tolkien", :title "The Lord of the Rings", :ISBN 9780618517657}

It is always possible to write functions wrapping the default constructor to get more complex construction semantics - using keyword arguments, supplying defaults and so on.

This seems like the ideal scenario for a macro to provide expanded semantics. What macros have people written and/or recommend for richer defrecord construction?

+2  A: 

Here is one approach: http://david-mcneil.com/post/765563763/enhanced-clojure-records

Ben Mabey
+5  A: 

Examples of support for full and partial record constructor functions and support for eval-able print and pprint forms:

David is a colleague of mine and we are using this defrecord2 extensively in our project. I think something like this should really be part of Clojure core (details might vary considerably of course).

The things we've found to be important are:

  • Ability to construct a record with named (possibly partial) parameters: (new-foo {:a 1})
  • Ability to construct a record by copying an existing record and making modifications: (new-foo old-foo {:a 10})
  • Field validation - if you pass a field outside the declared record fields, throw an error. Of course, this is actually legal and potentially useful, so there are ways to make it optional. Since it would be rare in our usage, it's far more likely to be an error.
  • Default values - these would be very useful but we haven't implemented it. Chas Emerick has written about adding support for default values here: http://cemerick.com/2010/08/02/defrecord-slot-defaults/
  • Print and pprint support - we find it very useful to have records print and pprint in a form that is eval-able back to the original record. For example, this allows you to run a test, swipe the actual output, verify it, and use it as the expected output. Or to swipe output from a debug trace and get a real eval-able form.
Alex Miller
Definitely agree with a non-defined field likely being an error (but useful when intentional). As one presumably knows the extension field is deliberate it should perhaps have a different function and straight assoc throw an error. I also wonder if meta-data on an extended record would be useful.
Alex Stoddard
+3  A: 

Here is one that defines a record with default values and invariants. It creates a ctor that can take keyword args to set the values of the fields.

(defconstrainedrecord Foo [a 1 b 2]
  [(every? number? [a b])])

(new-Foo)
;=> #user.Foo{:a 1, :b 2}

(new-Foo :a 42)
; #user.Foo{:a 42, :b 2}

And like I said... invariants:

(new-Foo :a "bad")
; AssertionError

But they only make sense in the context of Trammel.

fogus