Expected syntax for destructuring-bind

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})
1 Like

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.