The documentation on metadata claims that ^{:hi 10} 'x is equivalent to (with-meta 'x {:hi 10}), but I can't see that.
Evaluating the following on a repl,
(binding [*print-meta* true]
(prn ^{:hi 10} 'x)
(prn (with-meta 'x {:hi 10})))
prints the following, which shows that the first case doesn't get the metadata attached.
x
^{:hi 10} x
Am I doing something wrong?
^ is a reader macro which attaches metadata to the form that follows it. However, 'x is not a form to which metadata can be applied; it expands to (quote x) via the ' reader macro. When you type ^{:hi 10} 'x, the metadata gets attached to the un-evaluated (quote x) form and not the bare symbol x:
user> (set! *print-meta* true)
user> (prn (read-string "'x"))
(quote x)
user> (prn (read-string "^{:hi 10} 'x"))
^{:hi 10} (quote x)
However, evaluating a form with metadata does not carry the metadata through to the result:
user> (prn (eval (read-string "^{:hi 10} 'x")))
x
You can attach metadata to a quoted symbol by placing the ^ after the ', as in:
user> (prn (read-string "'^{:hi 10} x"))
(quote ^{:hi 10} x)
user> (prn '^{:hi 10} x)
^{:hi 10} x
Related
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.
In clojure, can this ever be true?
(= #'x #'y)
Examples of what doesn't work:
user> (def x 1)
#'user/x
user> (def y x)
#'user/y
user> (= #'x #'y)
false
user> (def y #'x)
#'user/y
user> (= #'x #'y)
false
(refer 'clojure.core :only '[=] :rename '{= equal?})
(= #'= #'equal?)
;= true
Is there a way to have an exception raised if the keys you're trying to destructure aren't in the map passed to your function? Would this be a good use case for a macro?
For example:
(defn x [{:keys [a b]}] (println a b))
I'd like this to work:
(x {:a 1 :b 2})
But this to raise an exception (:b is missing)
(x {:a 1})
What about a pre-condition?
(defn x [{:keys [a b]}]
{:pre [(some? a) (some? b)]}
(println a b))
user=> (x {:a 1})
AssertionError Assert failed: (some? b) user/x (NO_SOURCE_FILE:1)
Edit: Yes, you could use a macro to handle this for you. Maybe something like this:
(defmacro defnkeys [name bindings & body]
`(defn ~name [{:keys ~bindings}]
{:pre ~(vec (map #(list 'some? %) bindings))}
~#body))
(defnkeys foo [a b]
(println a b))
(foo {:a 1 :b 2})
(foo {:a 1}) ;; AssertionError Assert failed: (some? b)
(foo {:a 1 :b nil}) ;; AssertionError Assert failed: (some? b)
(defrecord Sample (x y))
(def check (Sample. 1 2))
(:x check) ;returns 1
If I receive (:x check) as an argument to a function, is there a way to access check? Or, in other words, return
#:user.Sample{:x 1, :y 2}
No, if a function is passed (:x check) as parameter then the value was already evaluated before entering the function, you'll just receive a 1 as value, and you can't retrieve the record it came from.
If you need the record inside the function, why don't you pass check as parameter?
As Óscar described, (:x check) won't work because its result is 1, and '(:x check) won't work because its result is a list containing the keyword :x and the symbol check.
However, instead of using quote you could use the list function:
(defn receiver [arg]
(map class arg))
;; With a quoted list it receives the symbol `check` rather
;; than the Sample record
(receiver '(:x check))
;=> (clojure.lang.Keyword clojure.lang.Symbol)
;; But this way it does receive the Sample
(receiver (list :x check))
;=> (clojure.lang.Keyword user.Sample)
And (list :x check) can be evaluated:
(eval (list :x check))
;=> 1
(defn receiver [arg]
(str "received " arg "; eval'd to: " (eval arg)))
(receiver (list :x check))
;=> "received (:x #user.Sample{:x 1, :y 2}); eval'd to: 1"
The reason that quote and list behave so differently is that quote doesn't evaluate it's argument. And, when a list is quoted, that effect is recursive: none of the items in the list are evaluated either.
There's another type of quote, called syntax-quote or backquote (and described on the Clojure.org page about the reader) which allows you to selectively un-quote (i.e. evaluate) items.
(require '[clojure.pprint])
(let [n 1
x :x
c check]
(clojure.pprint/pprint
(vector `(n x c)
`(~n x c)
`(~n ~x c)
`(~n ~x ~c))))
Prints:
[(user/n user/x user/c)
(1 user/x user/c)
(1 :x user/c)
(1 :x {:x 1, :y 2})]
And, actually, I lied a little bit. You could in fact use '(:x check) in this particular case. resolve will return the Var associated with a symbol, and deref (or its # reader macro) will get you the Var's value.
(resolve 'check)
;=> #'user/check
(deref (resolve 'check))
;=> #user.Sample{:x 1, :y 2}
(defn resolve-and-deref-symbols [form]
(map (fn [x] (if (symbol? x)
#(resolve x)
x))
form))
(defn receiver [arg]
(str "received " arg "; eval'd to: " (eval (resolve-and-deref-symbols arg))))
(receiver '(:x check))
;=> "received (:x check); eval'd to: 1"
I didn't mention it straight-away because, while it works easily enough in this example, it's not at all appropriate for the general case. (For instance, it won't work with locals, and handling namespaces and nested data structure would be painful).
I am creating records in Clojure and would like to set some fields up with a default value. How can I do this?
Use a constructor function.
(defrecord Foo [a b c])
(defn make-foo
[& {:keys [a b c] :or {a 5 c 7}}]
(Foo. a b c))
(make-foo :b 6)
(make-foo :b 6 :a 8)
Of course there are various variations. You could for example require certain fields to be non-optional and without a default.
(defn make-foo
[b & {:keys [a c] :or {a 5 c 7}}]
(Foo. a b c))
(make-foo 6)
(make-foo 6 :a 8)
YMMV.
You can pass initial values to a record pretty easily when you construct it though an extension map:
(defrecord Foo [])
(def foo (Foo. nil {:bar 1 :baz 2}))
In light of this, I usually create a constructor function that merges in some default values (which you can override as you want):
(defn make-foo [values-map]
(let [default-values {:bar 1 :baz 2}]
(Foo. nil (merge default-values values-map))))
(make-foo {:fiz 3 :bar 8})
=> #:user.Foo{:fiz 3, :bar 8, :baz 2}
After having the same question, I ended up wrapping the defrecord and the factory function up into a single definition using a macro.
The macro:
(defmacro make-model
[name args & body]
(let [defaults (if (map? (first body)) (first body) {})
constructor-name (str/lower-case (str "make-" name))]
`(do (defrecord ~name ~args ~#(if (map? (first body)) (rest body) body))
(defn ~(symbol constructor-name)
([] (~(symbol constructor-name) {}))
([values#] (~(symbol (str "map->" name)) (merge ~defaults values#)))))))
Usage
(make-model User [firstName lastName] {:lastName "Smith"})
=> #'user/make-user
(make-user {:firstName "John"})
=> #user.User{:firstName "John", :lastName "Smith"}