Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In clojure, when is it useful to define several symbols with same name but different metadata?

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.

like image 217
viebel Avatar asked Feb 21 '23 09:02

viebel


2 Answers

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.

like image 192
Michał Marczyk Avatar answered Apr 30 '23 10:04

Michał Marczyk


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.

like image 41
Gabriel Mitchell Avatar answered Apr 30 '23 10:04

Gabriel Mitchell