macro: retrieving metadata for an object bound to a symbol passed as argument

111 views
Skip to first unread message

wujek....@gmail.com

unread,
Jan 14, 2013, 9:27:38 AM1/14/13
to clo...@googlegroups.com
Hi. I am writing a small library (for learning purposes) and there are a few macros. I have a macro that defines some object with the user-specified name and internal some metadata:

(defmacro defobj [name operations] ; operations is a map
 `(def ~name (with-meta ~operations {::my-obj true})))

Then users (and the library itself will do it) can call it to define objects:

(defobj testobj {})
(meta testobj) ; gives {:user/my-obj true}

Now, I would like to create a macro that takes a name as an argument, and does something with the object, but first I would like to check if the object was created by 'defobj', i.e. check if the object's meta contains the key ::my-obj. That's what I can do, I don't know how to get the meta of the symbol:

(defmacro with-obj [name]
 (let [obj-meta (meta name)]
  (if (or (nil? obj-meta)) ; (not (::my-obj obj-meta)))
   (throw (IllegalArgumentException. (str name " seems not be our object")))))
 `(println "nice"))

This always fails, as the meta is not fetched correctly; for example:

(macroexpand '(with-obj testobj))
IllegalArgumentException clojure.core$name@48183a9a seems not be our object  user/with-obj (NO_SOURCE_FILE:4)

So I have a few questions:
1. should I attach the meta to the object, or to its var? or maybe I shouldn't do it at all, and just omit such checks altogether? (but I would still like to know how to make it work ;d)
2. how do I retrieve the meta of an object that is specified by a symbol passed to a macro by the user?
3. eventually, I would like my library to create a few default objects, so they would be in my namespace (say, 'my.lib'), and the users should be able to define their own objects as well in whatever namespace they wish; I would love it if the meta checking in the macro worked with objects in all namespaces, also if the symbols are namespaced or not
4. in the exception above, the symbol is resolved to the clojure.core/name, which is wrong as well; I know I can change the symbol name, but I have the feeling that this is not the way to go ;d

Could anybody help me with the macro in question and fetching the meta?

Regards,
wujek

Jim foo.bar

unread,
Jan 14, 2013, 9:32:37 AM1/14/13
to clo...@googlegroups.com
On 14/01/13 14:27, wujek....@gmail.com wrote:
> (defmacro with-obj [name]
> (let [obj-meta (meta name)]
> (if (or (nil? obj-meta)) ; (not (::my-obj obj-meta)))
> (throw (IllegalArgumentException. (str name " seems not be our
> object")))))
> `(println "nice"))
>


Try this:


(defmacro with-obj [name]
`(if-let [obj-meta# (meta ~name)]
(println "nice")
(throw (IllegalArgumentException. (str ~name " seems not be our
object")))))

HTH...

Jim


Jim foo.bar

unread,
Jan 14, 2013, 9:34:10 AM1/14/13
to clo...@googlegroups.com
Of course you should know that built-in java types do not support
meta-data...You need to implement IObj in order to provide meta-data
support to your own types...otherwise use records...

Jim

Jim foo.bar

unread,
Jan 14, 2013, 9:41:56 AM1/14/13
to clo...@googlegroups.com
Why does this have to be a macro? Why can't it be a first-class function
instead?

(defn with-obj [ob]
(if-let [obj-meta (meta ob)] name
(throw (IllegalArgumentException. (str ob " seems not be our
object")))))

Jim

Wujek Srujek

unread,
Jan 14, 2013, 9:42:27 AM1/14/13
to clo...@googlegroups.com
Thanks. If I use syntax-quoting, the error will be checked only at the time of executing the (macro-expanded) code, not at macro-expansion time, like some other checks I am performing. Is it possible to achieve what I want while the macro is called? The checks I am performing are some semantic checks, so maybe this should really be done later?
I know metas are only available for IMetas, but my objects will always be clojure maps, so this shouldn't be a problem. Do you think I should apply the metadata to the symbol?

Regards,
wujek


--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@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+unsubscribe@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en

Wujek Srujek

unread,
Jan 14, 2013, 9:45:28 AM1/14/13
to clo...@googlegroups.com
Because this is not the whole functionality ;d The check is just a fragment, the one that doesn't work. There is much more to it, like taking the object, taking some keys and values and using them in (let) and then executing some code in this context (which is another parameter, which is not in my example as I wanted to keep it simple).
If I could do it with a function, I would of course prefer it, but I am not sure if it can work.

wujek


Wujek Srujek

unread,
Jan 14, 2013, 9:56:21 AM1/14/13
to clo...@googlegroups.com
Actually, the with-object mecro is very similar to clojure.algo.monads/with-monad - it also lets a few bindings, like m-bind or m-result, and executes user-defined expressions in the context of the monad specified by a name. They just don't perform such checks, you can call with-monad with any symbol, and you will get an NPE:
user=> (require '[clojure.algo.monads :refer :all])
user=> (domonad identity-m [a 2 b 3] (* a b))
6
user=> (domonad whatever [a 2 b 3] (* a b))
NullPointerException   user/eval1803 (NO_SOURCE_FILE:1)

That's probably because the monad functions set up by with-monad are nils. (domonad uses with-monad under the hood.) If I perform my check, I will be able to give a much nicer error message, and there is a lot value in that.

wujek

Jim foo.bar

unread,
Jan 14, 2013, 10:25:19 AM1/14/13
to clo...@googlegroups.com
I am not sure I follow...Do you by any chance want the macro-expansion to lead you directly to one of your two options (printing or exception)? In this case you can do this:

(defmacro with-obj [ob]
 (if-let [obj-meta (meta ob)] (eval ob) ;not replacing any code but eval-ing on the fly

   (throw (IllegalArgumentException. (str ob " seems not be our object")))))

(defrecord Foo [a b])

user=> (macroexpand '(with-obj "Jim"))
IllegalArgumentException Jim seems not be our object  user/with-obj (NO_SOURCE_FILE:3)
user=> (macroexpand '(with-obj (with-meta (Foo. 'x 'y) {:t 1 :p 2})))
#user.Foo{:a x, :b y}

So now you get the exception or the actual object at macro-expansion time...Does that help at all?

Jim

For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@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

Wujek Srujek

unread,
Jan 14, 2013, 10:46:36 AM1/14/13
to clo...@googlegroups.com
When I invoke a macro:
(with-object someobject)
someobject is not evaluated, it is passed as a symbol to the macro. I need somehow to get the meta for the object that is hidden behind that symbol. So, I won't be passing objects in-line, as you did in your Foo example, I will first be doing:
(defobject foo {map of my functions / values})
(with-object foo (some-expressions))

Throwing an exception when meta is not there is one thing what I want (the missing part); the printing was just there to make the example shorter, it's not actually the real code - I don't need a macro for that. In reality, after all checks, there is code generated that desctructures the map in a (let) and executes some-expressions in this context, making certain bindings available for the expressions. This bit works fine, it's just the meta check that doesn't work as I don't know how to get the object that the symbol points to (in the macro, all I have is just a symbol):

user=> (def somename 17)
#'user/somename
user=> (defmacro testabc [name] (class name))
#'user/testabc
user=> (testabc somename)
clojure.lang.Symbol

When I change the macro like this:
user=> (defmacro testabc [name] name)

the object is returned:
user=> (testabc somename)
17

but it is because after macro expansion the returned symbol resolves to the object I set before. I would like to be able to get access to that object at macro call time.
But when I come to think about it now, I guess this would be impossible? Are the vars available when the macro is called? i.e.:

user=> (def somename 17)
user=> (testabc somename) ; <- in this macro, I need to take the 'somename' symbol and retrieve value 17 from above - is this possible at all?

Thanks for your help,
wujek

Jim foo.bar

unread,
Jan 14, 2013, 11:28:44 AM1/14/13
to clo...@googlegroups.com
aaa ok now I understand what you mean....I'm really sorry but I hadn't read all your original questions. So lets set things straight:


1. should I attach the meta to the object, or to its var? or maybe I shouldn't do it at all, and just omit such checks altogether? (but I would still like to know how to make it work ;d)
meta-data are always attached on the var if I'm not mistaken...


2. how do I retrieve the meta of an object that is specified by a symbol passed to a macro by the user?
the general answer here is that you use ~ inside a syntax-quote to jump right out of the quoting which results in evaluating the expression. If you're not in a syntax-quoted form then I can only think of 'eval' which will resolve the var anyway...


4. in the exception above, the symbol is resolved to the clojure.core/name, which is wrong as well; I know I can change the symbol name, but I have the feeling that this is not the way to go ;d
I'm not sure how to answer this...I know what t do inside the macro to avoid symbol capturing but I'm not sure what to do in the actual parameter list of the macro...


So, with all these in place I'd say you're looking for something like this:

(defmacro with-obj [ob]
 (if-let [obj-meta (eval `(meta ~ob))] (println "nice")

   (throw (IllegalArgumentException. (str ob " seems not be our object")))))


Let's see how it works:

user=> (def my-name "JIM")
#'user/my-name
user=> (defrecord NAME [^String s])
user.NAME
user=> (def your-name (NAME. "Wujek" {:surname "Srujek"} nil)) ;;your-name has meta-data
#'user/your-name
user=>  (macroexpand '(with-obj my-name))
IllegalArgumentException my-name seems not be our object  user/with-obj (NO_SOURCE_FILE:4)
user=>  (macroexpand '(with-obj your-name))
nice
nil

I think we 're getting there yes? Is this more helpful?

Jim

Wujek Srujek

unread,
Jan 14, 2013, 2:23:30 PM1/14/13
to clo...@googlegroups.com
Hi. Yes, the (eval `(meta ~name)) did the trick, it also seems to be working the way I would like it to in terms of namespaces, which is great! I must confess that I don't fully grok why it has to be that way (syntax-quote plus syntax escape plus eval), but I will just have learn more and hopefully I some day I will ;d Maybe this can be written easier, but this is good enough for me now. Thank you again, you've been of great help.

As for the meta for vars or objects, check this out:

user=> (def ^:var-meta testtest ^:obj-meta {:fn "wujek"})
#'user/testtest
user=> (meta testtest)
{:obj-meta true}
user=> (meta (var testtest))
{:ns #<Namespace user>, :name testtest, :var-meta true, :line 1, :file "NO_SOURCE_PATH"}
So the metas are different. You said yourself that only IMetas are supported as meta targets, which can be shown here:
user=> (def ^:var-meta testtest2 ^:obj-meta 17)
#<IllegalArgumentException java.lang.IllegalArgumentException: Metadata can only be applied to IMetas>

If metas were always applied to vars, there would be no difference as I guess all vars can habe metadata.

wujek
Reply all
Reply to author
Forward
0 new messages