I want to create a macro so that i can add the variable name inside the variable itself. I have this Unit record. Its name member stores the name of variable that would have been created.
i.e. like (def a-variable (->Unit 1 1 "a-varible"))
but i donot want to pass the variable name myself.
And i think macros can come handy. :)
Here is the code :
(defrecord Unit
[value gradient name]
Object
(toString [_]
(str name " : ")))
(defmacro create-unit1 [var-name & body]
`(def ~var-name ~(concat body (list (name var-name)))))
(defmacro create-unit2 [var-name & body]
`(def ~var-name (concat ~#body (list (name '~var-name)))))
But none of them is giving me write code :
neurals.core> (macroexpand '(create-unit1 a (->Unit 1.0 0.0)))
(def a ((->Unit 1.0 0.0) "a"))
neurals.core> (macroexpand '(create-unit2 a (->Unit 1.0 0.0)))
(def a (clojure.core/concat
(->Unit 1.0 0.0) (clojure.core/list
(clojure.core/name (quote a)))))
neurals.core>
I wanted :
(def a (->Unit 1.0 0.0 (clojure.core/name (quote a))))
What is the right way to execute the concat inside the macro ?
You can fix your code by removing the & in create-unit1:
(defmacro create-unit1 [var-name body]
`(def ~var-name ~(concat body (list (name var-name)))))
(macroexpand `(create-unit1 a (->Unit 1.0 1.0)))
;; => (def user/a (user/->Unit 1.0 1.0 "a"))
You could also add a little bit of syntactic sugar:
(defmacro create-unit1 [var-name body]
`(def ~var-name (~#body ~(name var-name))))
Or, to keep all of that more idiomatic:
(defmacro defunit [sym value gradient]
`(def ~sym (->Unit ~value ~gradient ~(name sym))))
(defunit a 1.0 1.0)
;; => #user.Unit{:value 1.0, :gradient 1.0, :name "a"}
Related
Suppose i want to change a number in a loop to a keyword in order to check an entry in a dict/map:
(def myarray {:p1 "test"})
#'user/myarray
user> (get myarray
(keyword ":p1")
)
nil
user> (get myarray
(symbol ":p1")
)
nil
user>
I am just getting nil returned. What do i miss here?
: is the indicator of keyword according to the Clojure guide, and the keyword function adds : automatically according to the Clojure Docs. So the correct code must be (keyword "p1") instead of (keyword ":p1").
Here is an outline of how you can do it:
(ns tst.demo.core
(:use demo.core tupelo.core tupelo.test))
(dotest
(let [m1 {:p1 "a"
:p2 "b"}
k1 (keyword (str "p" 1))
k2 (keyword (str "p" 2))
v1 (k1 m1)
v2 (k2 m1)]
(is= [k1 k2] [:p1 :p2])
(is= [v1 v2] ["a" "b"])))
Note though, an integer is a perfectly valid map key:
(dotest
(let [m1 {1 "a"
2 "b"}
k1 1
k2 2
v1 (get m1 k1) ; this doesn't work: (k1 m1 )
v2 (get m1 k2)]
(is= [k1 k2] [1 2])
(is= [v1 v2] ["a" "b"])))
You just have to use the get function (with the map as 1st arg!).
The above code is based on this template project.
This macro returns the values of the "magic" &env as a map, so that
(let [xyz "ZYX"] (get-env)) returns {xyz "ZYX"}, where the key is a Symbol.
(defmacro get-env []
(let [ks (keys &env)]
`(zipmap '~ks [~#ks])))
The expression '~ks evaluates the ks into Symbols at the macro-expansion phase (right?), but then quotes the expansion, so that the Symbols don't get evaluated into their values ("ZYX" in our example), but rather stay as Symbols (xyz). Is that right?
About [~#ks]: It evaluates ks into an seq of Symbols at the macro-expansion phase (right?) (and splices them and forms a vector with []). But how does that allow these Symbols to get further evaluated into their values ("ZYX" in our example) -- is there a second evaluation step, applied immediately after the first?
Another variant is
(defmacro local-env [] (->> (keys &env)
(map (fn [k] [(list 'quote k) k])) (into {})))
Your macro takes all the keys from the env. Then it uses the keys (a
list of symbols) to zip both the list of keys with spliced symbols
inside a vector. So what you get from
(let [x 42]
(get-env))
is
(let [x 42]
(zipmap '(x) [x]))
This is a compile-time transformation of your code (the whole point of
macros). The resulting code at runtime will use the 42 from the bound
x.
Preface
You may also be interested in the book "Clojure Macros", and in this StackOverflow question:
How do I write a Clojure threading macro?
Discussion
When in doubt, ask the compiler. Consider this code using my favorite template project:
(ns tst.demo.core
(:use demo.core tupelo.core tupelo.test))
(defmacro getenv []
(prn :env &env)
(prn :env-meta (meta &env))
(prn :form &form)
(prn :form-meta (meta &form)))
(defn go []
(newline)
(prn :01)
(getenv)
(let [x 1
y "yyy"]
(newline)
(prn :02)
(getenv))
)
(dotest
(go))
with output
:env nil
:env-meta nil
:form (getenv)
:form-meta {:line 15, :column 3}
:env {x #object[clojure.lang.Compiler$LocalBinding 0x1ab07559 "clojure.lang.Compiler$LocalBinding#1ab07559"], y #object[clojure.lang.Compiler$LocalBinding 0x26c79134 "clojure.lang.Compiler$LocalBinding#26c79134"]}
:env-meta nil
:form (getenv)
:form-meta {:line 21, :column 5}
Testing tst.demo.core
:01
:02
so we can see the 4 (prn ...) outputs for each call to getenv. In the case where there are no local bindings, we get
&env ;=> nil
and for the case with the let we get a map like
(let [env-val (quote
{x :compiler-local-1
y :compiler-local-1})
ks (keys env-val)
ks-vec [ks]
]
(spyx env-val)
(spyx ks)
(spyx ks-vec)
)
with result
env-val => {x :compiler-local-1, y :compiler-local-1}
ks => (x y)
ks-vec => [(x y)]
At this point, I'm not quite sure what your desired result is. Could you modify the question to add that?
BTW, there is no hidden 2nd step, if I understand your question correctly.
Also
I rewrite your local-env and got the following result:
(defmacro local-env []
(prn :into-result
(into {}
(mapv
(fn [k] [(list 'quote k) k])
(keys &env)))))
(let [x 1
y "yyy"]
(newline)
(prn :03)
(local-env))
with result
:into-result {(quote x) x,
(quote y) y}
so I think there is some confusion here.
Why does this fail:
(eval (with-meta '(fn [] 0) {:stack (gensym "overflow")}))
; Syntax error compiling at (REPL:1:1).
; Unable to resolve symbol: overflow210 in this context
when none of the following fail?
(eval (with-meta '(do [] 0) {:stack (gensym "overflow")}))
; 0
(eval (with-meta '(let [] 0) {:stack (gensym "overflow")}))
; 0
(eval (with-meta '(if true 0 1) {:stack (gensym "overflow")}))
; 0
(eval (with-meta '(println "hello") {:stack (gensym "overflow")}))
; hello
; nil
The examples above are my attempt to find a minimal, reproducible example. I ran into this question when working on a macro, with a simplified example here:
(defmacro my-macro []
(with-meta '(fn [] 0) {:stack (gensym "overflow")}))
(my-macro)
; Syntax error compiling at (REPL:1:1).
; Unable to resolve symbol: overflow156 in this context
while attempting to follow the model explained in this post on testing Clojure macros.
Great question! This was fun to dig into.
It's helpful to first start by trying to set the metadata map to something that works, and retrieve it in each of your examples:
(meta (eval (with-meta '(fn [] 0) {:ten 10})))
;;=> {:ten 10}
(meta (eval (with-meta '(do [] 0) {:ten 10})))
;;=> nil
(meta (eval (with-meta '(let [] 0) {:ten 10})))
;;=> nil
(meta (eval (with-meta '(if true 0 1) {:ten 10})))
;;=> nil
(meta (eval (with-meta '(println "hello") {:ten 10})))
;; printed: hello
;;=> nil
Hopefully this gives you some idea of what's happening here: the metadata isn't returned as part of the value of the non-fn forms, so it's not evaluated. We can test this hypothesis with another value that uses its metadata, like a vector:
(meta (eval (with-meta '[1] {:ten 10})))
;;=> {:ten 10}
but with a gensym:
(eval (with-meta '[1] {:stack (gensym "overflow")}))
;;=> Syntax error compiling at (tmp:localhost:35479(clj)*:25:7).
;;=> Unable to resolve symbol: overflow6261 in this context
You can see where this is emitted in the Clojure compiler, and searching for new MetaExpr will show you the other places where metadata evaluation is emitted (I could see vectors, maps, sets, functions, and reify).
tl,dr: Clojure evaluates the metadata for function forms because it attaches the evaluated metadata to the resulting function. This is also true of the data literals supported in Clojure's syntax. Any other metadata on forms is stripped away by compilation, so it doesn't get evaluated and thus doesn't cause symbol resolution errors.
The problem is your usage of gensym, not the metadata. Observe:
(ns tst.demo.core
(:use demo.core tupelo.core tupelo.test))
(dotest
(binding [*print-meta* true]
(let [
fn-code-plain '(fn [] 42)
fn-code-meta (with-meta '(fn [] 42) {:alpha true})
fn-code-sym-meta (with-meta '(fn [] 42) {:alpha (gensym "dummy")})
]
(prn fn-code-plain)
(prn fn-code-meta)
\
(prn (eval fn-code-plain))
(prn (eval fn-code-meta))
(newline)
(prn fn-code-sym-meta)
(throws? (eval fn-code-sym-meta))
)
))
with result:
^{:line 7, :column 27} (fn [] 42)
^{:alpha true} (fn [] 42)
#object[user$eval19866$fn__19867 0xac891b5 "user$eval19866$fn__19867#ac891b5"]
^{:alpha true} #object[user$eval19870$fn__19871 0x2d1ab7e0 "user$eval19870$fn__19871#2d1ab7e0"]
^{:alpha dummy19865} (fn [] 42)
The problem is that eval sees the symbol dummy19865 and tries to resolve it. The fact that it was created by gensym is irrelevant. No problem with a keyword:
fn-code-kw-meta (with-meta '(fn [] 42) {:alpha :dummy-42})
<snip>
(prn fn-code-kw-meta)
(prn (eval fn-code-kw-meta))
producing:
^{:alpha :dummy-42} (fn [] 42)
^{:alpha :dummy-42} #object[user$eval19953$fn__19954 0x13253ac7 "user$eval19953$fn__19954#13253ac7"]
or a symbol that is defined:
(def mysym "Forty-Two!")
<snip>
fn-code-mysym-meta (with-meta '(fn [] 42) {:alpha mysym})
<snip>
(prn fn-code-mysym-meta)
(prn (eval fn-code-mysym-meta))
with result:
^{:alpha "Forty-Two!"} (fn [] 42)
^{:alpha "Forty-Two!"} #object[user$eval20082$fn__20083 0x24a63de5 "user$eval20082$fn__20083#24a63de5"]
Summary:
You have demonstrated that eval only attempts symbol resolution of metadata for the fn form, but not other special forms like do, let, if, or with pre-existing functions such as println. If you wish to explore further, you should probably inquire at the Clojure email list:
clojure#googlegroups.com
The above code is based on this template project.
Need function or macro, which takes const parameter(route) and dynamic parameters (args) and return concatenated string of parameters:
user>(defn full-url [route & args] *need code* )
#'user/full-url
user> (def p1 "value1")
#'user/p1
user> (def p2 "value2")
#'user/p2
user> (def p3 "value3")
#'user/p3
user> (full-url "/init" p1 p2 p3)
"/init?p1=value1&p2=value2&p3value4"
Any ideas?
First, a macro to do what you want:
(defmacro full-url
[route & args]
`(let [var-names# (map #(str %1 "=") '~args)
var-vals# (list ~#args)
joined# (clojure.string/join "&" (map str var-names# var-vals#))]
(str ~route "?" joined#)))
Now, I would add that I do not think this is the best approach as it ties the names of your vars to the param names. IMO a better approach is to use a regular function that takes a map as a second argument, that has keywords and values. Such as:
(defn full-url-fn
[route params]
(->> params
(map #(str (name (first %)) "=" (second %)))
(clojure.string/join "&")
(str route "?")))
(full-url-fn "/init" {:p1 "value1" :p2 "value2"})
But, either way should work.
I'm writing a macro to allow pass the clauses as a parameter to functions:
(defmacro parse-cmd [command & body]
(let [parts (str/split command #" ")
cmd (first parts)
args (into [] (rest parts))
clauses (partition 2 2 body)]
`(case ~cmd
~#(mapcat (fn [c] [(nth c 0) `(apply ~(nth c 1) ~args)]) clauses))))
(defn mysum [a b]
(+ (Integer. a) (Integer. b)))
(parse-cmd "SET 1 1" "SET" "GET" println)
2
This works well when cmd is a string, however with a var:
(def cmd "SET 1 1")
(parse-cmd cmd "SET" "GET" println)
I get ClassCastException clojure.lang.Symbol cannot be cast to java.lang.CharSequenceq clojure.string/split (string.clj:222)
I guess I should prevent the evaluation of the let too, but I can't make it work:
(defmacro parse-cmd [command & body]
`(let [parts# (str/split ~command #" ")
cmd# (first parts#)
args# (into [] (rest parts#))
clauses# (partition 2 2 ~body)]
(case cmd#
(mapcat (fn [c#] [(nth c# 0) `(apply ~(nth c# 1) args#)]) clauses#))))
With this definition I get:
ClassCastException java.lang.String cannot be cast to clojure.lang.IFn kvstore.replication/eval12098 (form-init7453673077215360561.clj:1)
let's macroexpand this (for your second macro)
(parse-cmd "SET 1 1" "SET" mysum "GET" println)
it expands to:
(let [parts__31433__auto__ (str/split "SET 1 1" #" ")
cmd__31434__auto__ (first parts__31433__auto__)
args__31435__auto__ (into [] (rest parts__31433__auto__))
clauses__31436__auto__ (partition
2
2
("SET" mysum "GET" println))]
(case
cmd__31434__auto__
(mapcat
(fn [c__31437__auto__] [(nth c__31437__auto__ 0)
(seq
(concat
(list 'apply)
(list (nth c__31437__auto__ 1))
(list 'args__31432__auto__)))])
clauses__31436__auto__)))
there are two problems here:
1) you generate this code: ("SET" mysum "GET" println), which obviously causes your exception, because "SET" is not a function
2) you generate the wrong case expression, I see that you have forgotten to unquote-splice your mapcat
Let's try to fix this:
first of all unquote mapcat; then you can move clauses out of your generated let, because it can be totally done at compile-time:
(defmacro parse-cmd [command & body]
(let [clauses (partition 2 2 body)]
`(let [parts# (str/split ~command #" ")
cmd# (first parts#)
args# (into [] (rest parts#))]
(case cmd#
~#(mapcat (fn [c] [(nth c 0) `(apply ~(nth c 1) args#)]) clauses)))))
now let's check the expansion:
(let [parts__31653__auto__ (str/split "SET 1 1" #" ")
cmd__31654__auto__ (first parts__31653__auto__)
args__31655__auto__ (into [] (rest parts__31653__auto__))]
(case
cmd__31654__auto__
"SET"
(apply mysum args__31652__auto__)
"GET"
(apply println args__31652__auto__)))
ok. looks better. let's try to run it:
(parse-cmd "SET 1 1" "SET" mysum "GET" println)
we have another error now:
CompilerException java.lang.RuntimeException: Unable to resolve symbol: args__31652__auto__ in this context, compiling:(*cider-repl ttask*:2893:12)
so expansion also shows us this:
args__31655__auto__ (into [] (rest parts__31653__auto__))
...
(apply mysum args__31652__auto__)
so there are different symbols for args# here. That's because the scope of the generated symbol name is one syntax-quote. So inner syntax-quote with apply generates the new one. You should use gensym to fix that:
(defmacro parse-cmd [command & body]
(let [clauses (partition 2 2 body)
args-sym (gensym "args")]
`(let [parts# (str/split ~command #" ")
cmd# (first parts#)
~args-sym (into [] (rest parts#))]
(case cmd#
~#(mapcat (fn [c] [(nth c 0) `(apply ~(nth c 1) ~args-sym)]) clauses)))))
ok now it should work properly:
ttask.core> (parse-cmd "SET 1 1" "SET" mysum "GET" println)
2
ttask.core> (parse-cmd cmd "SET" mysum "GET" println)
2
great!
I would also recommend you to use destructuring in a mapcat function and quoted let, to make it more readable:
(defmacro parse-cmd [command & body]
(let [clauses (partition 2 2 body)
args-sym (gensym "args")]
`(let [[cmd# & ~args-sym] (str/split ~command #" ")]
(case cmd#
~#(mapcat (fn [[op fun]] [op `(apply ~fun ~args-sym)]) clauses)))))
But if it's not just an exercise in writing macros, you shouldn't use the macro for that, since you pass just string and function references here, so anyway you shall evaluate everything in runtime.
(defn parse-cmd-1 [command & body]
(let [[cmd & args] (str/split command #" ")
commands-map (apply hash-map body)]
(apply (commands-map cmd) args)))