In clojure, two symbols a
and b
might have the same name but different metadata. The symbols are then =
but not identical?
.
For example:
(def a (with-meta 'cool {:real-name 'unknown}))
(def b (with-meta 'cool {:real-name 'undefined}))
(identical? a b); false
(= a b); true
It seems to be very powerful. I would like to see a real life use case of this feature of the language.
Please share your original thoughts.
I'm afraid this use case is Rich's idea and not mine, but perhaps it will be of interest:
The language itself uses this feature in def
forms (and convenience macros wrapping def
, including defn
and defmacro
), because metadata attached to the symbols naming the Vars being created is transferred to the Vars themselves. It can then be used by the compiler and various tools.
One might point out that defn
& Co. optionally take an attribute map argument which gets merged into the Vars metadata, so using metadata on the symbolic name is not necessary from the user's point of view. Plain def
, however, does not, and internally defn
expands to a def
form with the metadata coming from the attribute map attached to the Var name itself. (For the sake of completeness, it is possible to modify a Var's metadata map in a separate step after its creation, but this is not what happens in defn
.)
All this is not hidden from the programmer at all, by the way. On the contrary, attaching metadata to the symbol "by hand" is in many cases more concise than using the attribute map (perhaps one could even say more idiomatic). Clojure's own standard library uses this style (and I mean post-bootstrap) -- for example, both clojure.core
and clojure.string
define Vars named replace
, but only the latter is tagged with the return value type (namely String
):
;;; clojure.core
(defn replace
...)
;;; clojure.string
(defn ^String replace
...)
^String
here is transformed to {:tag String}
, which is attached to the symbol replace
at read time and later used by the compiler (as a type hint). Other uses include marking Vars as private with ^:private
(in Clojure 1.3, ^{:private true}
in 1.2), attaching docstrings to Vars created with plain def
(the only way to do it in 1.2; 1.3 allows an extra docstring argument, but ^{:doc "..."}
is still being used) etc.
Similarly, in ClojureScript, you could have two functions named foo
only one of which should be exported (living in different namespaces, of course). In that case you'd say
(defn ^:export foo ...)
in one namespace and
(defn foo ...)
in the other; ^:export
gets translated to ^{:export true}
at read time, this gets merged into the metadata of this occurrence of the symbol foo
and then read and acted upon by the ClojureScript compiler.
I do this in some software I am hacking to do probabilistic analyses of stochastic processes. You can tell me whether or not is ``real life''. In general metadata is useful for providing context about sets or elements in a set that is independent of their value. For example I can imagine a universal set, the set of reals and and element contained in the reals
(def Reals (with-meta 'Reals {:universe 'U}))
(def x (with-meta 'x {:universe Reals}))
Now, the reals are an uncountable set, but my element might be contained it a much smaller set like the rationals or integers. Suppose, I happened to know, through some extra information or as as the result of some other analysis certain elements that belonged to the integers and that I used this information to analyze some elements and add containment metadata
(def known-integers #{'x 'y 'z})
(defn find-known-integers [& s] (filter #(contains? known-integers %) (with-meta s {:universe 'Integers})))
I can now write
user=> (find-known-integers 'x 'a)
(x)
But I also have
user=> (= x (first (find-known-integers 'x 'a)))
true
user=> (identical? x (first (find-known-integers 'x 'a)))
false
so that even though both elements have the same value they are not considered identical, since the version returned by the find-known-integers functions is known to be contained in a much smaller universe than the original. Of course, this information about containment is in some sense ``subjective'', which is why it is useful to think about it as metadata, since different analyses might yield different (and perhaps even contradictory) results for a variable.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With