The main use I've had for with-meta is in macros, to attach e.g. type
hints to a symbol that's going into the macro expansion. There, the ^
reader macro adds the metadata too early rather than with-meta adding
it too late: ^ hints some symbol in the macro body and the compiler
will apply the type hint in compiling the macro itself, rather than in
compiling whatever the macro outputs as its expansion.

So, ^String x ......... x in ordinary functions, x ........
~(with-meta x {:tag 'String}) in macros.

To recap, hinting x in the macro is useless, because x, a variable in
the macro itself, isn't what you want to hint; some symbol in the
output is what you want to hint. Another variation is ~(with-meta
(gensym) {:tag 'SomeClass}).

You might also want to fully qualify the classname when it's not in
java.lang; otherwise the *source files where the macro is called* will
have to :import the appropriate package. So, ~(with-meta x {:tag
'com.something.somepackage.SomeClass}). Remember, the macro
effectively just replaces its own call with its own output, so if you
have

(ns foo
  :import (java.util.concurrent ConcurrentHashMap))

(defn bar [x]
  (my-macro [a x] (do-something-with a)))

and my-macro type-hints something as String, you effectively are
compiling something like:

(ns foo
  :import (java.util.concurrent ConcurrentHashMap))

(defn bar [x]
  (let [^String a x]
    (if (macro-supplied-condition-on-a)
      (do-something-with a)
        (default-macro-supplied-action)))))

and String resolves because it's in java.lang. If the type hint is
ConcurrentHashMap:


(ns foo
  :import (java.util.concurrent ConcurrentHashMap))

(defn bar [x]
  (let [^ConcurrentHashMap a x]
    (if (macro-supplied-condition-on-a)
      (do-something-with a)
        (default-macro-supplied-action)))))

and ConcurrentHashMap resolves because the file that *uses* the macro
imports it. If the type hint is JPanel:


(ns foo
  :import (java.util.concurrent ConcurrentHashMap))

(defn bar [x]
  (let [^JPanel a x]
    (if (macro-supplied-condition-on-a)
      (do-something-with a)
        (default-macro-supplied-action)))))

#<CompilerException java.lang.IllegalArgumentException: Unable to
resolve classname: JPanel (foo.clj:5)>


On the other hand if it's javax.swing.JPanel it works:

(defmacro make-hinted-caller [name class meth]
  (let [x (gensym)]
    `(defn ~name [~(with-meta x {:tag class})] (~meth ~x))))

user=> (defn foo [x] (.getUI x))
#'user/foo
user=> (foo 3)
#<CompilerException java.lang.IllegalArgumentException: No matching
field found: getUI for class java.lang.Integer (NO_SOURCE_FILE:228)>
user=> (make-hinted-caller foo javax.swing.JPanel .getUI)
#'user/foo
user=> (foo 3)
#<CompilerException java.lang.ClassCastException: java.lang.Integer
cannot be cast to javax.swing.JPanel (NO_SOURCE_FILE:230)>
user=> (JPanel.)
#<CompilerException java.lang.IllegalArgumentException: Unable to
resolve classname: JPanel (NO_SOURCE_FILE:232)>
user=> (make-hinted-caller foo JPanel .getUI)
#<CompilerException java.lang.IllegalArgumentException: Unable to
resolve classname: JPanel (NO_SOURCE_FILE:233)>

The first exception is easily recognized as one thrown by the
reflection API. Without the hint the .getUI call goes through
reflection.

The second exception is a normal ClassCastException from trying to
coerce an Integer to a JPanel. This shows that the make-hinted-caller
macro replaced foo with a type-hinted version that expects a JPanel.

The third exception shows that JPanel had not been :imported into the
user namespace in that REPL instance.

The fourth exception is the result of trying to use the unqualified
name in the macro.

Here's the exception happening if a macro that hints an arg as JPanel
is defined in one namespace WITH JPanel imported and used in another
WITHOUT the import:

user=> (ns foo (:import (javax.swing JPanel)))
javax.swing.JPanel
foo=> (defmacro make-hinted-getui-caller [name]
  (let [x (gensym)]
    `(defn ~name [~(with-meta x {:tag 'JPanel})] (.getUI ~x))))
#'foo/make-hinted-getui-caller
foo=> (ns bar)
nil
bar=> (foo/make-hinted-getui-caller foo)
#<CompilerException java.lang.IllegalArgumentException: Unable to
resolve classname: JPanel (NO_SOURCE_FILE:245)>
bar=>

-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en

Reply via email to