In clojure, metadata is idiomatically a persistent map. Further, anything can implement the IMeta interface, or the more common IObj interface (there are similar protocols in cljs), and it will now have metadata capabilities. When using the ^ reader macro that denotes metadata, the default inference is that ^some-type will associate metadata of {:tag some-type} with the following object (clojure literals like vectors, maps, sets, lists, symbols all support metadata). If you want more explicit control, you can pass in a map literal to be the actual metadata, e.g. ^{:type long}x .
This would be no different than constructing a symbol, e.g. via gensym, and giving it metadata…
(use 'clojure.pprint)
(binding [*print-meta* true]
(pprint (let [x (with-meta (gensym "x") {:tag 'long})]
`[~x] )))
;;[^long x21037]
Per the docs, metadata must be a symbol, keyword, string, or map. Maps are inferred to be the literal metadata, keywords are inferred as metadata of {:the-keyword true}, and strings/symbols are inferred as {:tag the-string|the-symbol} metadata for type hinting. You rarely see strings in practice, although there are cases (as with java’s weird primitive array type) where they show up. Since you can supply a map literal, you can put pretty much anything in there; it’s just the form that the reader expects (e.g., the syntax sugar) infers the aforementioned behavior from a limited set of types. metadata is commonly used to pack supplemental information; you just throw stuff into a map basically. This is particularly useful for metaprogramming, since you can pack annotations about the forms and expressions in the metadata and access it as needed. The only hurdle is that the default defmacro does not include the metadata from the input form by default, hence my original example to use &form to get that information back (or write your own defmacro that does this by default).