symbol is not a symbol in (def (symbol "x") ...) ???

138 views
Skip to first unread message

Rostislav Svoboda

unread,
Oct 2, 2020, 3:38:50 PM10/2/20
to clo...@googlegroups.com
I have a problem with `def` in macros. I'd like to create a bunch of following definitions:

(def foo "FOO")
(def bar "BAR")
(def baz "BAZ")

So I wrote a macro:

user=> (defmacro def-stuff [v] `(def (symbol ~v) (clojure.string/upper-case ~v)))
#'user/def-stuff

And I'd like to do map this macro over a vector of strings:

user=> (map (fn [n] (def-stuff n)) ["foo" "bar" "baz"])
Syntax error compiling def at (REPL:1:14).
First argument to def must be a Symbol

A little check indicates that everything should be fine:
user=> (symbol? (symbol "foo"))
true

but it is not:
user=> (def (symbol "foo") "FOO")
Syntax error compiling def at (REPL:1:1).
First argument to def must be a Symbol

???
Interestingly enough:

user=> (defmacro def-stuff [v] `(def v (clojure.string/upper-case ~v)))
#'user/def-stuff
user=> (map (fn [n] (def-stuff n)) ["foo" "bar" "baz"])
(#'user/v #'user/v #'user/v)

Strangely enough a `v` gets (re)defined.

To me it looks like the lexical-binding of macro-parameters is ignored if there's a `(def <macro-param> ...)` inside this macro.

Can anybody explain what's going on here please? Thanks.

tome...@gmail.com

unread,
Oct 3, 2020, 3:55:20 PM10/3/20
to Clojure
`def` is a special form and it expects exactly a symbol as a first argument (https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Compiler.java#L541). That means that you can't do: (def (symbol "a") 1).

To generate `defs` from macro you can do the following:

(defmacro def-stuff
  [names]
  `(do ~@(for [n names]
           `(def ~(symbol n)
              ~(clojure.string/upper-case n)))))

(def-stuff ["foo" "bar" "baz"])

foo
;; => "FOO"

(macroexpand '(def-stuff ["foo" "bar" "baz"]))
;; => (do (def foo "FOO") (def bar "BAR") (def baz "BAZ"))

Peter Hull

unread,
Oct 5, 2020, 5:41:53 AM10/5/20
to Clojure
On Friday, 2 October 2020 at 20:38:50 UTC+1 Bost wrote:
I have a problem with `def` in macros. I'd like to create a bunch of following definitions:

Also possible to do without macros:
(defn def-stuff [xs] (for [x xs] (intern *ns* (symbol x) (upper-case x))))
This seems to work for me, if anyone knows why it's not right, I'd love to hear.

tome...@gmail.com

unread,
Oct 5, 2020, 6:31:35 AM10/5/20
to Clojure
Yes, that's almost good solution and works on runtime.
Almost because I would use `doseq` instead of `for`. `for` generates lazy seq.

Jeremy Heiler

unread,
Oct 5, 2020, 7:02:44 AM10/5/20
to clo...@googlegroups.com
Do you know about macroexpand?

user=> (defmacro m1 [v] `(def (symbol ~v)))
#'user/m1
First argument to def must be a Symbol
user=> (macroexpand '(m1 "foo"))
(def (clojure.core/symbol "foo"))
user=> (defmacro m2 [v] `(def ~(symbol v)))
#'user/m2
user=> (macroexpand '(m2 "foo"))
(def foo)

The trick here is that you need to convert v into a symbol when the macro is expanded, not at runtime.

--
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+u...@googlegroups.com
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 unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/clojure/CAEtmmex6Kn%3DQ8AZ-AyYoZLpYVu7%2BWnX3u7vQBgydiA%2B1jUt9Rw%40mail.gmail.com.

Rostislav Svoboda

unread,
Oct 5, 2020, 8:02:22 AM10/5/20
to clo...@googlegroups.com
This is pure gold - your answers! Thanks a lot. So the take outs and key ideas for me are:

- The syntax quote '`', unquote '~' and I expect also the unquote splicing '~@' can be nested.
- A global var doesn't need to be created by the `def` (in macro). Use the `intern` function directly (and be aware of possible lazy evaluation).
- There's more to `macroexpand` than just the debugging. Think out of the box!*

At the first glance I like the "use `intern`" idea at most and I'll give it a try it the following form:
    (defn def-stuff [x] (intern *ns* (symbol x) x))
    (map def-stuff [...])
This way, I naively hope, I can reason about it the functional-programming way:
    Just create a function and map it over something. No code generation or expansion trickery involved.

Again, thank you very much, I consider adding your examples to https://clojuredocs.org/clojure.core/def

Bost

*as always :)


tome...@gmail.com

unread,
Oct 5, 2020, 8:09:11 AM10/5/20
to Clojure
One quick hint, since `intern` is a function with side effects (mutates ns state) I would use `run!` instead of `map` (which is lazy).

Cheers!

Reply all
Reply to author
Forward
0 new messages