missingfaktor
missingfaktor

Reputation: 92076

First-class class/record instantiation

I have a sequence of records/classes, and I map over that sequence with new and expect to get a sequence of instances of those records/classes. I know new is a special form, but I was expecting Clojure to do The Right Thing in this case.

But this does not work:

(map new [SomeClass1 SomeClass2 SomeClass3])

Neither does this.

(map #(new %) [SomeClass1 SomeClass2 SomeClass3])

Similar code works in Factor.

{ SomeClass1 SomeClass2 SomeClass3 } [ new ] map

What would be the right way to do this in Clojure? (I expect it won't involve Class.newInstance ugliness.)

Edit:

The following works, but is perhaps slower than necessary. (I don't know for sure. I would appreciate some information on this.)

(map #(eval `(new ~%)) [SomeClass1 SomeClass2 SomeClass3])

Also I am looking for something more elegant.

Upvotes: 4

Views: 204

Answers (2)

Ankur
Ankur

Reputation: 33657

As new is a special form, one of the solution to make it work like first class would be use the clojure low level calls like:

(map #(clojure.lang.Reflector/invokeConstructor %1 (into-array [])) [String])

This may lead to the performance problems of Reflection hence the macro based solution are prefered over this.

Upvotes: 1

Arthur Ulfeldt
Arthur Ulfeldt

Reputation: 91587

because special-formes are well... special they are not first class and don't compose as proper functions do, you can solve this with eval and macros:

a solution using eval:

(defn fnew [c] (eval `(new ~c))) 
hello.core> (map fnew ['Exception 'java.lang.String])
(#<Exception java.lang.Exception> "")

and a version that takes arguments to the constructors:

(defn fnew [c] (eval `(new ~@c))) 

hello.core> (map fnew ['(Exception) '(java.lang.String "I am a contructor argument")])
(#<Exception java.lang.Exception> "I am a contructor argument")

(map fnew [ [Exception] [(class "foo") "I am a contructor argument"]])
(#<Exception java.lang.Exception> "I am a contructor argument")

and here is a macro example

hello.core> (defmacro newify [classes] (apply vector (for [c classes] `(new ~c))))
#'hello.core/newify

hello.core> (macroexpand '(newify [java.lang.String Exception]))
[(new java.lang.String) (new Exception)]

hello.core>  (newify [java.lang.String Exception])
["" #<Exception java.lang.Exception>]

The macro version is likely more efficient while the eval version is more flexible.

Upvotes: 3

Related Questions