Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Recommended macros to add functionality to Clojure's defrecord constructor?

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?

like image 675
Alex Stoddard Avatar asked Oct 04 '10 15:10

Alex Stoddard


3 Answers

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

  • http://david-mcneil.com/post/765563763/enhanced-clojure-records
  • http://github.com/david-mcneil/defrecord2

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.
like image 121
Alex Miller Avatar answered Nov 14 '22 03:11

Alex Miller


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.

like image 24
fogus Avatar answered Nov 14 '22 03:11

fogus


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

like image 23
Ben Mabey Avatar answered Nov 14 '22 02:11

Ben Mabey