Am 07.02.2011 um 18:20 schrieb Justin Kramer:
> Checking out clojure.core/destructure and clojure.core/let might be
> helpful.
>
> Here's a macro I hacked together. It doesn't work with :keys, :as,
> or :or. I take no responsibility if you use it for anything real. But
> maybe it will provide you with some ideas.
>
> (defmacro destructure->map
> [bvec val]
> `(let [~bvec ~val]
> (-> {}
> ~@(for [s (remove #(= '& %) (flatten bvec))]
> `(assoc (-> (quote ~s) name keyword) ~s)))))
>
> Example:
>
> (let [v [1 2 [3 4 5 6 7 8]]]
> (destructure->map [a b [c d & e]] v))
> => {:e (5 6 7 8), :d 4, :c 3, :b 2, :a 1}
Straight-forward, ugly loop solution:
(defn destruct
[pattern coll]
(loop [ps (seq pattern)
s (seq coll)
ls {}]
(let [local (first ps)]
(cond
; patterns exhausted? done
(nil? ps)
ls
; "local" is a seq? => descent
(seq? local)
(recur (next ps) (next s) (merge ls (destruct local (first s))))
; "local" is something & like? => bind the rest and done.
(= "&" (name local))
(assoc ls (second ps) s)
; default => bind the local in the map.
:else
(recur (next ps) (next s) (assoc ls local (first s)))))))
user=> (destruct '(a b (c d & e)) [1 2 [3 4 5 6 7 8]])
{c 3, d 4, e (5 6 7 8), b 2, a 1}
Sincerely
Meikel
Am 07.02.2011 um 20:26 schrieb CuppoJava:
> I was wondering whether the "destructure" function can be written
> using the existing "let" form. Thanks for your help.
Ah ok. Nevermind.
Sincerely
Meikel
Am 07.02.2011 um 20:51 schrieb CuppoJava:
> Thanks for your answer Meikel.
>
> Your answer isn't that ugly. It's very similar to what I have as
> well.
>
> I would like to know if you think it's possible to re-use "let" to do
> this. I feel like I'm re-inventing the wheel somewhat.
I don't think you can use let without resorting to macros since let itself is one. Once in macro, always in macro world.
As fogus said: you could copy the logic from c.c/destructure and make it do the calls instead of emitting code. But I don't think there is a pre-fabricated function doing that.
Sincerely
Meikel
Arguably there should be, with the let macro calling it to do the
heavy lifting but also available for direct user use.
I've thought about that actually. And it wouldn't work.
So (defn destructure [form value]
...magic...)`(let* ~(vec (destructure forms body)) <- at this point, "body" is
(defmacro let [forms body]
not known yet. It's just a symbol.
~@body)
This approach won't work because "body" is only a symbol within the
macro. We don't know it's value until runtime.
-Patrick
What? While the macro is executing "body" will be bound to an
s-expression (basically, source code parsed to an AST but no further;
source code as processed by the reader).
What was being discussed is easy in principle to accomplish. As it
stands we have something like
(defmacro let [binding-form body]
`(let* ~(some-complex-code-that-emits-a-vector binding-form)
~@body))
already; all that's needed is to lift
"some-complex-code-that-emits-a-vector" out to an explicit function
with binding-form as the sole argument, whose output is a vector of
alternating names and sexps.
This will then do as a general, runtime-usable destructure function
that turns vectors resembling binding forms into flattened vectors.
You can get the desired maps with a simple additional process of
(reduce (fn [a [b c]] (assoc a b c)) {} (partition 2 the-vector)).
(Maybe (into {} (partition 2 the-vector)) will DTRT with repeated
keys, too.)
What?
No. The syntax for let is a vector, so it'd be something like
(def a [1 2 3])
(let [[x y z] a]
(println x y z))
and we would have:
binding-forms '[[x y z] a]
body '(println x y z)
Furthermore I don't see what you need the body for.
In the binding-forms, it's true that the destructured input is not
known yet. The macro must emit something like
(let* [x (first a)
xxx (rest a)
y (first xxx)
yyy (rest xxx)
z (first yyy)]
(println x y z))
in cases like this. In short, the code it emits into the let* bindings
does the destructuring.
This code could be made to do double duty as follows.
First, whenever it can emit an s-expression like '(first a) or '(rest
a) it can check a flag and decide to either emit the s-expression or
do the equivalent thing, e.g. (first a), immediately on its argument
a.
Second, the let macro can generate the let* binding vector by calling
the function with the flag set to emit-s-expressions.
Third, the user-usable function can generate input to (into {}
(partition 2 ...)) by calling the function with the flag set to do-it.
It boils down to a few cases, like replacing
`(first ~sym)
with (if emit-sexps? `(first ~sym) (first sym))
and similarly elsewhere in the code that gets abstracted out into the helper-fn.
Then let becomes
(defmacro let [bindings & body]
`(let* ~(helper-fn bindings true)
~@body))
or something similar and the user destructure function is
(defn destructure [bindoids]
(into {} (partition 2 (helper-fn bindoids false))))