Clojure record constructors not first class?

2.3k views Asked by At

Apparently, you can't call apply with a record constructor:

(defrecord Foo. [id field])

(apply Foo. my-list)

fails at read time because it is not expecting Foo. in that place.

The only obvious workaround I could think of was to add a factory function:

(make-foo [id field] (Foo. id field))

which can be apply'ed of course.

Am I missing anything? I'd expect this from C#/Java but just thought it was a bit disappointing in Clojure...

3

There are 3 answers

0
Alex Miller On BEST ANSWER

Circling back on this post-1.3....

In Clojure 1.3, defrecord creates two generated constructor functions. Given:

(defrecord Person [first last]) 

this will create a positional constructor function ->Person:

(->Person "alex" "miller")

and a map constructor function map->Person:

(map->Person {:first "string"})

Because this is a map, all keys are optional and take on a nil value in the constructed object.

You should require/use these functions from the ns where you declare the record, but you do not need to import the record class as you would when using the Java class constructor.

More details:

0
nickik On

The problem is known and there is lots of talk about it on the Clojure mailing list. More support will probably be added in future Clojure versions.

For now you have to use your own functions or use https://github.com/david-mcneil/defrecord2 which supports some features like:

  • print in an eval'able form
  • provide clojure function as constructor
  • accept named parameters (maps) in constructor
  • participate in pre/post walk multi-method
3
Alex Miller On

Foo. is a Java class constructor so it has typical Java interop constraints with how you call it. Creating a constructor function is a common solution (it also means you don't have to import the Foo when in a different namespace).