19

In Clojure, given a class name as a string, I need to create a new instance of the class. In other words, how would I implement new-instance-from-class-name in

(def my-class-name "org.myorg.pkg.Foo")
; calls constructor of org.myorg.pkg.Foo with arguments 1, 2 and 3
(new-instance-from-class-name  my-class-name 1 2 3) 

I am looking for a solution more elegant than

  • calling the Java newInstance method on a constructor from the class
  • using eval, load-string, ...

In practice, I will be using it on classes created using defrecord. So if there is any special syntax for that scenario, I would be quite interested.

4 Answers 4

26

There are two good ways to do this. Which is best depends on the specific circumstance.

The first is reflection:

(clojure.lang.Reflector/invokeConstructor
  (resolve (symbol "Integer"))
  (to-array ["16"]))

That's like calling (new Integer "16") ...include any other ctor arguments you need in the to-array vector. This is easy, but slower at runtime than using new with sufficient type hints.

The second option is as fast as possible, but a bit more complicated, and uses eval:

(defn make-factory [classname & types]
  (let [args (map #(with-meta (symbol (str "x" %2)) {:tag %1}) types (range))]
    (eval `(fn [~@args] (new ~(symbol classname) ~@args)))))

(def int-factory (make-factory "Integer" 'String))

(int-factory "42")

The key point is to eval code that defines an anonymous function, as make-factory does. This is slow -- slower than the reflection example above, so only do it as infrequently as possible such as once per class. But having done that you have a regular Clojure function that you can store somewhere, in a var like int-factory in this example, or in a hash-map or vector depending on how you'll be using it. Regardless, this factory function will run at full compiled speed, can be inlined by HotSpot, etc. and will always run much faster than the reflection example.

When you're specifically dealing with classes generated by deftype or defrecord, you can skip the type list since those classes always have exactly two ctors each with different arities. This allows something like:

(defn record-factory [recordname]
  (let [recordclass ^Class (resolve (symbol recordname))
        max-arg-count (apply max (map #(count (.getParameterTypes %))
                                      (.getConstructors recordclass)))
        args (map #(symbol (str "x" %)) (range (- max-arg-count 2)))]
    (eval `(fn [~@args] (new ~(symbol recordname) ~@args)))))


(defrecord ExampleRecord [a b c])

(def example-record-factory (record-factory "ExampleRecord"))

(example-record-factory "F." "Scott" 'Fitzgerald)
Sign up to request clarification or add additional context in comments.

1 Comment

Excellent! The second option is obviously a very general technique. I've already used it in another way.
4

Since 'new' is a special form, I'm not sure there you can do this without a macro. Here is a way to do it using a macro:

user=> (defmacro str-new [s & args] `(new ~(symbol s) ~@args))
#'user/str-new
user=> (str-new "String" "LOL")
"LOL"

Check out Michal's comment on the limitations of this macro.

2 Comments

Note that this will only work if the s received by the macro is a (literal) string and not an arbitrary expression evaluating to a string. In the latter case, there's no avoiding eval or reflective instance construction.
I am afraid that s will not be a literal string. I have edited the question to reflect this.
3

Here is a technique for extending defrecord to automatically create well-named constructor functions to construct record instances (either new or based on an existing record).

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

Comments

3

In Clojure 1.3, defrecord will automatically defn a factory function using the record name with "->" prepended. Similarly, a variant that takes a map will be the record name prepended with "map->".

user=> (defrecord MyRec [a b])
user.MyRec
user=> (->MyRec 1 "one")
#user.MyRec{:a 1, :b "one"}
user=> (map->MyRec {:a 2})
#user.MyRec{:a 2, :b nil}

A macro like this should work to create an instance from the string name of the record type:

(defmacro newbie [recname & args] `(~(symbol (str "->" recname)) ~@args))

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.