In clojure, when is it useful to define multiple characters with the same name but with different metadata?

In clojure, the two characters a and b may have the same name but different metadata. Characters then = but not identical? .

For instance:

 (def a (with-meta 'cool {:real-name 'unknown})) (def b (with-meta 'cool {:real-name 'undefined})) (identical? ab); false (= ab); true 

It seems to be very powerful. I would like to see a real case of using this feature of the language.

Share your original thoughts.

+4
source share
3 answers

I'm afraid this use case is an idea of ​​God, not mine, but maybe it will be interesting:

The language itself uses this function in def forms (and the transfer of convenient def macros, including defn and defmacro ), because the metadata attached to the symbols denoting the created Vars is transmitted by Vars themselves. Then it can be used by the compiler and various tools.

You can indicate that defn and Co. optionally accepts an attribute map argument, which is combined into Vars metadata, so using metadata in a symbolic name is optional from the user's point of view. Normal def , however, does not, and internally defn expands to def with metadata coming from the attribute map attached to the Var name itself. (For completeness, you can change the Var metadata map at a separate stage after its creation, but this is not what happens in defn .)

All this is by no means hidden from the programmer. On the contrary, attaching metadata to a symbol “manually” is in many cases shorter than using an attribute map (perhaps you can 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 with the name replace , but only the latter is marked with the return type (namely String ):

 ;;; clojure.core (defn replace ...) ;;; clojure.string (defn ^String replace ...) 

^String here is converted to {:tag String} , which is attached to the replace character while reading and is later used by the compiler (as a type hint). Other uses include marking Vars as confidential with ^:private (in Clojure 1.3, ^{:private true} in 1.2), attaching docstrings to Vars created with simple def (the only way to do this in 1.2; 1.3 allows for additional docstring argument, but ^{:doc "..."} is still used), etc.

Similarly, in ClojureScript you can have two functions named foo , only one of which must be exported (of course, lives in different namespaces). In this case you will say

 (defn ^:export foo ...) 

in one namespace and

 (defn foo ...) 

in another; ^:export translates to ^{:export true} while reading, this is combined into the metadata of this occurrence of the foo character, and then it is read and processed by the ClojureScript compiler.

+3
source

I do this in some kind of software that I hack to do a probabilistic analysis of random processes. You can tell me if this is really “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, a lot of reals and elements contained in actions

 (def Reals (with-meta 'Reals {:universe 'U})) (def x (with-meta 'x {:universe Reals})) 

Variables are now an uncountable set, but my element may contain a much smaller set of it, such as rational or integer numbers. Suppose I happened to learn through some additional information or as a result of some other analysis of some elements belonging to 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}))) 

Now i can 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, although 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 in the original. Of course, this information about deterrence is in some sense “subjective,” so it’s useful to think of it as metadata, since different analyzes can give different (and possibly even contradictory) results for a variable.

+3
source

Remember that identical? actually comparing not values ​​+ metadata, but references to objects, therefore identical? is the same as the == operator in Java. Because of this, you will have:

 => (def a 'sym) => (def b 'sym) => (= ab) true => (identical? ab) false 

It is possible to distinguish between variables with different metadata with this method (because inside they seem to be different objects), but not vice versa: you cannot conclude that the vars with the same metadata will always be identical? :

 user=> (def d (with-meta 'sym { :a :b })) #'user/d user=> (def e (with-meta 'sym { :a :b })) #'user/e user=> (= de) true user=> (identical? de) false 
+2
source

Source: https://habr.com/ru/post/1397649/


All Articles