Thanks for the useful information. What I don’t yet understand is given a sequence which the caller has attached meta data to, how can I get the meta programmatically? E.g., suppose the sequence as 10 objects, any of which might be equal to each other, and some of which might have meta data, how can I get the meta data ?
You cannot pass in a list as meta, but you can pass in a map like I showed. If you pass in the list form unquoted, it will be eval’d at read-time (in this instance), and yield a result of Boolean, since Boolean is bound to java.lag.Boolean, and is truthy, which will return a result for or
.
(binding [*print-meta* true]
(pprint (let [x (with-meta (gensym "x") {:type '(or Boolean (and Number (not Long)))})]
`[~x])))
[^{:type ^{:line 4925, :column 53}
(or Boolean
^{:line 4925, :column 65}
(and Number
^{:line 4925, :column 77} (not Long)))} x21123]
Another option is to encode with vectors, or maps even.
(binding [*print-meta* true]
(pprint (let [x (with-meta (gensym "x") {:type '[or Boolean [and Number [not Long]]]})]
`[~x])))
[^{:type [or Boolean [and Number [not Long]]]} x21118]
Wait, I’m confused. Are you saying when I pass a sequence to a function, and I’ve put meta data on the items of the sequence, then the function CANNOT extract the meta data? That’s what I’m asking how to do. I think either you misunderstood my question, or I did a poor job of asking it. Sorry about that.
All roads lead to invoking meta
on the object. One way would be to destructure the vector, another could be using nth
or map
to get at the elements:
user> (let [v '[^Boolean a [^String b c] & ^Boolean d] [a [b c] & [_ & [d]]] v] [(meta a) (meta d)])
[{:tag Boolean} {:tag Boolean}]
It’s not so easy.
clojure-rte.core> (map meta [^Boolean 'a 'b ^Number 'c])
(nil nil nil)
clojure-rte.core> (meta [^Boolean 'a 'b ^Number 'c])
nil
clojure-rte.core>
I expected the map to return something like [{:tag Boolean} nil {:tag Boolean}]
Are you saying when I pass a sequence to a function, and I’ve put meta data on the items of the sequence, then the function CANNOT extract the meta data?
Not all all; metadata is intended for common uses like this:
user>(let [xs (-> (for [i (range 10)] ^{:hello "world"} [i])
vec
(with-meta {:vector? :yup}))]
(println (meta xs))
(reduce (fn [acc x] (conj acc {:entry x :meta (meta x)})) [] xs))
{:vector? :yup}
[{:entry [0], :meta {:hello "world"}}
{:entry [1], :meta {:hello "world"}}
{:entry [2], :meta {:hello "world"}}
{:entry [3], :meta {:hello "world"}}
{:entry [4], :meta {:hello "world"}}
{:entry [5], :meta {:hello "world"}}
{:entry [6], :meta {:hello "world"}}
{:entry [7], :meta {:hello "world"}}
{:entry [8], :meta {:hello "world"}}
{:entry [9], :meta {:hello "world"}}]
user> (map meta '[^Boolean a b ^Number c])
({:tag Boolean} nil {:tag Number})
ahhh OK,
clojure-rte.core> (meta [^Boolean 'a 'b ^Number 'c])
nil
clojure-rte.core>
(nil nil nil)
clojure-rte.core> (map meta '[^Boolean a b ^Number c])
({:tag Boolean} nil {:tag Number})
clojure-rte.core>
Yes and indeed as you predicted, lists are not allowed as meta data unfortunately.
(map meta '[^(or Number Boolean) a b ^Number c])
Syntax error reading source at (REPL:10336:51).
Metadata must be Symbol,Keyword,String or Map
3. Unhandled clojure.lang.ExceptionInfo
(No message)
{:clojure.error/phase :read-source}
main.clj: 410 clojure.main/repl/read-eval-print/fn
main.clj: 409 clojure.main/repl/read-eval-print
main.clj: 435 clojure.main/repl/fn
main.clj: 435 clojure.main/repl
main.clj: 345 clojure.main/repl
RestFn.java: 137 clojure.lang.RestFn/applyTo
core.clj: 665 clojure.core/apply
core.clj: 660 clojure.core/apply
regrow.clj: 20 refactor-nrepl.ns.slam.hound.regrow/wrap-clojure-repl/fn
RestFn.java: 1523 clojure.lang.RestFn/invoke
interruptible_eval.clj: 79 nrepl.middleware.interruptible-eval/evaluate
interruptible_eval.clj: 56 nrepl.middleware.interruptible-eval/evaluate
interruptible_eval.clj: 145 nrepl.middleware.interruptible-eval/interruptible-eval/fn/fn
AFn.java: 22 clojure.lang.AFn/run
session.clj: 202 nrepl.middleware.session/session-exec/main-loop/fn
session.clj: 201 nrepl.middleware.session/session-exec/main-loop
AFn.java: 22 clojure.lang.AFn/run
Thread.java: 834 java.lang.Thread/run
2. Caused by clojure.lang.LispReader$ReaderException
java.lang.IllegalArgumentException: Metadata must be
Symbol,Keyword,String or Map
{:clojure.error/line 10336, :clojure.error/column 51}
LispReader.java: 314 clojure.lang.LispReader/read
LispReader.java: 216 clojure.lang.LispReader/read
LispReader.java: 205 clojure.lang.LispReader/read
core.clj: 3768 clojure.core/read
core.clj: 3741 clojure.core/read
interruptible_eval.clj: 103 nrepl.middleware.interruptible-eval/evaluate/fn
main.clj: 410 clojure.main/repl/read-eval-print/fn
main.clj: 409 clojure.main/repl/read-eval-print
main.clj: 435 clojure.main/repl/fn
main.clj: 435 clojure.main/repl
main.clj: 345 clojure.main/repl
RestFn.java: 137 clojure.lang.RestFn/applyTo
core.clj: 665 clojure.core/apply
core.clj: 660 clojure.core/apply
regrow.clj: 20 refactor-nrepl.ns.slam.hound.regrow/wrap-clojure-repl/fn
RestFn.java: 1523 clojure.lang.RestFn/invoke
interruptible_eval.clj: 79 nrepl.middleware.interruptible-eval/evaluate
interruptible_eval.clj: 56 nrepl.middleware.interruptible-eval/evaluate
interruptible_eval.clj: 145 nrepl.middleware.interruptible-eval/interruptible-eval/fn/fn
AFn.java: 22 clojure.lang.AFn/run
session.clj: 202 nrepl.middleware.session/session-exec/main-loop/fn
session.clj: 201 nrepl.middleware.session/session-exec/main-loop
AFn.java: 22 clojure.lang.AFn/run
Thread.java: 834 java.lang.Thread/run
1. Caused by java.lang.IllegalArgumentException
Metadata must be Symbol,Keyword,String or Map
LispReader.java: 960 clojure.lang.LispReader$MetaReader/invoke
LispReader.java: 285 clojure.lang.LispReader/read
LispReader.java: 1398 clojure.lang.LispReader/readDelimitedList
LispReader.java: 1347 clojure.lang.LispReader$VectorReader/invoke
LispReader.java: 285 clojure.lang.LispReader/read
LispReader.java: 220 clojure.lang.LispReader/read
LispReader.java: 41 clojure.lang.LispReader/access$200
LispReader.java: 771 clojure.lang.LispReader$WrappingReader/invoke
LispReader.java: 285 clojure.lang.LispReader/read
LispReader.java: 1398 clojure.lang.LispReader/readDelimitedList
LispReader.java: 1243 clojure.lang.LispReader$ListReader/invoke
Yeah, I think the semantics that are non-obvious are the behavior of reading (and evaluating, keep in mind clojure treats vector, map, set literals as forms as well, which go through an evaluation pass) and quoting prior to evaluation. I think in your initial example, the metadata was not being preserved as you thought. With the quoted form of the vector, all the read-time metadata is preserved. In the initial variant, those symbols were evaluated (quote …) and the metadata associated with the list (quote a) was elided from the final result (I think).
So another option is to do what spec does, and register your types somewhere associated with keys, then have them as metadata. The other option is as I demonstrated, just pass in a map wrapping the form you’d like, e.g. {:type '(some complex form (of stuff))}
perhaps for my application, the only reasonable thing is to define the syntax so that the user gives a lambda-list, a possibly-empty map, and a function body. The map will map variable names to type restrictions. But I could also examine the meta data of the lambda-list for additional, very simple, type hints which are limited to a symbol (which is a common case).
(destructuring-case (some-function-call)
[^Boolean a [^String b c] & ^Boolean d] {}
(println [:first a b c d])
[^Boolean a b] {b (or String Boolean)}
(println [:second a b])
[a b] {a (and Number (not Long))
b (or String Boolean)}
(println [:third a b])
I think the [destrucure input] binding form is perfectly acceptable, akin to clojure.core.match and other libraries. Your original destructuring-case
is close if not fine already. It’s idiomatic for destructuring within let
and other binding forms (for
, loop
, etc), so it would be familiar to the casual observer. Bind the thing on the right to the thing on the left, in this case only if the case matches the predicate on the right. In this case, you’re using a specific semantics of binding that uses pattern matching and type predicates. If you go that route, you end up with a more flexible macro-based approach
(destructuring-case (some-function-call)
[[a [b c] & d] (Boolean [a d] String b)]
(do (println [:first])
(println [a b c d]))
[[a b] (Boolean a (or String Boolean) b)]
(do (println [:second])
(println [a b])))))
Thanks joinr for all your comments. It is all insightful.
This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.