Help writing a simple destructuring function.

16 views
Skip to first unread message

CuppoJava

unread,
Feb 7, 2011, 10:49:25 AM2/7/11
to Clojure
Hello everyone,

I am trying to write the following function:

-----------------------------------------------------------

Suppose form = [1 2 [3 4 5 6 7 8]]

(destructure '(a b (c d & e)) form)

should return:

{:a 1 :b 2 :c 3 :d 4 :e [5 6 7 8]}

------------------------------------------------------------

Now this is not too difficult of a function to write from scratch. BUT
it really looks like I should be able to piggy-back off the existing
functionality in the destructuring-let macro. But I cannot figure out
how I can do that without using eval. I'm not sure if it's even
possible.

Does anyone know if it's possible to implement this function in terms
of "let" and without using "eval"? Thank you very much for your help!
-Patrick

Justin Kramer

unread,
Feb 7, 2011, 12:20:11 PM2/7/11
to Clojure
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}


Justin

Fogus

unread,
Feb 7, 2011, 1:50:24 PM2/7/11
to Clojure
I have a library called Evalive (http://github.com/fogus/evalive) that
could help. Specifically I added a macro named `destro` that will do
_mostly_ what you want. Basically, it looks like the following:

(destro [a b [c d & e]] [1 2 [3 4 5 6 7 8]])

Which returns:


{vec__2183 [1 2 [3 4 5 6 7 8]],
a 1,
b 2,
vec__2184 [3 4 5 6 7 8],
c 3,
d 4,
e (5 6 7 8)}

:f

CuppoJava

unread,
Feb 7, 2011, 2:26:35 PM2/7/11
to Clojure
Sorry, I wasn't being very clear about my needs.

I need this function to run at *run-time*.

So the following:
(def values [1 2 [3 4 5 6 7 8]])
(def form '(a b (c d & e)))
(destructure form values)

Should return:
{:a 1 :b 2 :c 3 :d 4 :e [5 6 7 8]}

I was wondering whether the "destructure" function can be written
using the existing "let" form. Thanks for your help.
-Patrick

Meikel Brandmeyer

unread,
Feb 7, 2011, 2:43:16 PM2/7/11
to clo...@googlegroups.com
Hi,

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

Meikel Brandmeyer

unread,
Feb 7, 2011, 2:44:45 PM2/7/11
to clo...@googlegroups.com
Hi,

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


CuppoJava

unread,
Feb 7, 2011, 2:51:40 PM2/7/11
to Clojure
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.

-Patrick

Meikel Brandmeyer

unread,
Feb 7, 2011, 3:01:58 PM2/7/11
to clo...@googlegroups.com
Hi,

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

Ken Wesson

unread,
Feb 7, 2011, 3:32:06 PM2/7/11
to clo...@googlegroups.com

Arguably there should be, with the let macro calling it to do the
heavy lifting but also available for direct user use.

CuppoJava

unread,
Feb 7, 2011, 4:46:52 PM2/7/11
to Clojure
Actually that would be fine as a solution as well. I'm stumped as to
how to avoid repeating the same thing twice (once in function world
and once in macro world).

ie. Let's assume that we have this destructuring function. How do we
use that do program a destructuring let macro?

-Patrick

Alan

unread,
Feb 7, 2011, 7:01:24 PM2/7/11
to Clojure
(defn destructure [binding-form]
...magic...)

(defmacro let [forms body]
`(let* ~(vec (destructure forms))
~@body))

CuppoJava

unread,
Feb 7, 2011, 10:06:09 PM2/7/11
to Clojure
I've thought about that actually. And it wouldn't work.

So (defn destructure [form value]
...magic...)

(defmacro let [forms body]
`(let* ~(vec (destructure forms body)) <- at this point, "body" is
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

David Nolen

unread,
Feb 7, 2011, 10:42:11 PM2/7/11
to clo...@googlegroups.com
On Mon, Feb 7, 2011 at 10:06 PM, CuppoJava <patrick...@hotmail.com> wrote:
I've thought about that actually. And it wouldn't work.

So (defn destructure [form value]
      ...magic...)

(defmacro let [forms body]
 `(let* ~(vec (destructure forms body))  <- at this point, "body" is
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 you want isn't really that simple at all. What you're asking for is basically a halfway step towards unification.

David 

Ken Wesson

unread,
Feb 7, 2011, 11:45:16 PM2/7/11
to clo...@googlegroups.com
On Mon, Feb 7, 2011 at 10:06 PM, CuppoJava <patrick...@hotmail.com> wrote:
> I've thought about that actually. And it wouldn't work.
>
> So (defn destructure [form value]
>       ...magic...)
>
> (defmacro let [forms body]
>  `(let* ~(vec (destructure forms body))  <- at this point, "body" is
> 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.

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.)

CuppoJava

unread,
Feb 8, 2011, 12:31:48 AM2/8/11
to Clojure
Thanks for the reply Ken.

"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). "

This is the part where it falls apart. "body" is actually bound to a
single symbol.

For example in the destructuring-let macro:
(def a [1 2 3])
(let ((x y z) a)
(println x y z))

"body" refers to the single symbol "a".

Anyway, it seems like what I want is impossible, or at the very least,
difficult. I will just have to put up with re-implementing let.

Thanks for everyone's contribution

-Patrick


On Feb 7, 11:45 pm, Ken Wesson <kwess...@gmail.com> wrote:

Ken Wesson

unread,
Feb 8, 2011, 2:22:50 AM2/8/11
to clo...@googlegroups.com
On Tue, Feb 8, 2011 at 12:31 AM, CuppoJava <patrick...@hotmail.com> wrote:
> Thanks for the reply Ken.
>
> "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). "
>
> This is the part where it falls apart. "body" is actually bound to a
> single symbol.
>
> For example in the destructuring-let macro:
> (def a [1 2 3])
> (let ((x y z) a)
>   (println x y z))
>
> "body" refers to the single symbol "a".

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))))

Reply all
Reply to author
Forward
0 new messages