dynamically generated let bindings

57 views
Skip to first unread message

Sam Aaron

unread,
Jul 28, 2011, 6:48:34 PM7/28/11
to clo...@googlegroups.com
Hi there,

I'm trying to create a fn which does the following:

* returns a fn which takes an arbitrary number of args
* calls a helper fn, passing the incoming args returning a vector of alternating symbols and vals
* creates a let form using the vector of alternating symbols and vals returned by the helper fn as the bindings
* contains some inner form that makes use of the bindings

i.e. is it possible to implement something that allows the following to work:

(defn binding-vec
[args]
['size (count args)])

(defn mk-dynamically-bound-fn
[form]
;; returns a fn with sign [& args] which
;; uses binding-vec to create a the vec for
;; which a let statement binds around form
)

(def a (mk-dynamically-bound-fn '(+ size 10)))

(a 1 2 3 4) ;=> 14 (the number of args + 10)


Please let me know if I'm being a bit crazy with the question. It's totally possible that I'm barking up the wrong tree with this line of enquiry.

Sam

---
http://sam.aaron.name

Kent

unread,
Jul 28, 2011, 7:46:58 PM7/28/11
to Clojure
I'm not sure what you're trying to do with this and, based on that
ignorance, I'm not sure I think it's a great idea. Maybe you are being
a bit crazy, and maybe your a genius. Who am I to say?

Here is a function that does what you want. The only difference is
that my function also takes the "binding-vec" function as an argument.


(defn mdbf [form binding-func]
(fn [& args]
(eval `(let ~(binding-func args) ~form))))

Here it is used in your example.

(defn binding-vec
[args]
['size (count args)]

(def a (mdbf '(+ size 10) binding-vec))

(a 1 2 3 4) => 14

Kent.

Ken Wesson

unread,
Jul 28, 2011, 8:08:40 PM7/28/11
to clo...@googlegroups.com
On Thu, Jul 28, 2011 at 6:48 PM, Sam Aaron <sama...@gmail.com> wrote:
> Hi there,
>
> I'm trying to create a fn which does the following:
>
> * returns a fn which takes an arbitrary number of args
> * calls a helper fn, passing the incoming args returning a vector of alternating symbols and vals
> * creates a let form using the vector of alternating symbols and vals returned by the helper fn as the bindings
> * contains some inner form that makes use of the bindings

You probably want a macro rather than a normal function here, which is
used in the body of a function or else in a manner similar to defn.

--
Protege: What is this seething mass of parentheses?!
Master: Your father's Lisp REPL. This is the language of a true
hacker. Not as clumsy or random as C++; a language for a more
civilized age.

Alan Malloy

unread,
Jul 28, 2011, 9:12:39 PM7/28/11
to Clojure
On Jul 28, 3:48 pm, Sam Aaron <samaa...@gmail.com> wrote:
It's not clear how much of this is relevant to your actual problem, vs
the simple version you're posting here. If all you want to do is
automatically have the args counted for you, you could:

(defn wrap-counting [f]
(fn [& args]
(apply f (count args) args)))

(def a (wrap-counting
(fn [size & args]
(+ size 10))))

If you want something more general, that implicitly binds things for
you based on some code somewhere else (ew ew ew), then you need a
macro. This implementation is pretty gross; I suspect it could be
better, but you're trying to do something that seems weird to me.

(defn binding-vec []
['size '(count args)])

(defmacro magic-fn
[& forms]
`(fn [& ~'args]
(let ~(binding-vec)
~@forms)))


user> ((magic-fn (+ size 10)) 1 2)
12

Jeff Rose

unread,
Jul 29, 2011, 12:49:18 AM7/29/11
to Clojure
I don't think it's very typical to pass a form to a function, unless
you plan on using eval at runtime. If it doesn't need to happen at
runtime, then you'd do this with a macro approximately like this:

user=> (defmacro bar [f] `(fn [& args#] (let [~'size (count args#)]
~f)))
#'user/bar
user=> (bar (+ size 10))
#<user$eval675$fn__676 user$eval675$fn__676@11b4c2>
user=> (*1 1 2 3 4)
14

~Jeff

Ken Wesson

unread,
Jul 29, 2011, 2:22:28 AM7/29/11
to clo...@googlegroups.com
On Fri, Jul 29, 2011 at 12:49 AM, Jeff Rose <ros...@gmail.com> wrote:
> I don't think it's very typical to pass a form to a function, unless
> you plan on using eval at runtime.

Or it's a function called by a macro to do some processing of forms.

Sam Aaron

unread,
Jul 29, 2011, 4:49:57 AM7/29/11
to clo...@googlegroups.com

On 29 Jul 2011, at 07:22, Ken Wesson wrote:

> On Fri, Jul 29, 2011 at 12:49 AM, Jeff Rose <ros...@gmail.com> wrote:
>> I don't think it's very typical to pass a form to a function, unless
>> you plan on using eval at runtime.
>
> Or it's a function called by a macro to do some processing of forms.

Yep, this is precisely what I was considering. I should have explicitly made mk-dynamically-bound-fn a macro in my example - a miscommunication on my part.


On 29 Jul 2011, at 02:12, Alan Malloy wrote:

>
> It's not clear how much of this is relevant to your actual problem, vs
> the simple version you're posting here.

Sorry, I was just trying to simplify things to try and get directly to the meat of the problem.

> If you want something more general, that implicitly binds things for
> you based on some code somewhere else (ew ew ew), then you need a
> macro. This implementation is pretty gross; I suspect it could be
> better, but you're trying to do something that seems weird to me.
>
> (defn binding-vec []
> ['size '(count args)])
>
> (defmacro magic-fn
> [& forms]
> `(fn [& ~'args]
> (let ~(binding-vec)
> ~@forms)))
>
>
> user> ((magic-fn (+ size 10)) 1 2)
> 12

Very cool, although it's not quite what I'd like. I'd like binding-vec to be a function of the resulting magic-fn's args.

I'm aiming for something like this, but can't get it to quite work:

(defn binding-vec [foos]
'[size (count ~foos)])

(defmacro magic-fn
[& forms]
`(fn [& ~'args]

(let ~(binding-vec ~'args)
~@forms)))

((magic-fn (+ size 10)) 1 2) ;=> BOOM

Sam

---
http://sam.aaron.name

Sam Aaron

unread,
Jul 29, 2011, 4:55:28 AM7/29/11
to clo...@googlegroups.com

On 29 Jul 2011, at 00:46, Kent wrote:

> I'm not sure what you're trying to do with this and, based on that
> ignorance, I'm not sure I think it's a great idea. Maybe you are being
> a bit crazy, and maybe your a genius. Who am I to say?
>
> Here is a function that does what you want. The only difference is
> that my function also takes the "binding-vec" function as an argument.
>
>
> (defn mdbf [form binding-func]
> (fn [& args]
> (eval `(let ~(binding-func args) ~form))))
>
> Here it is used in your example.
>
> (defn binding-vec
> [args]
> ['size (count args)]
>
> (def a (mdbf '(+ size 10) binding-vec))
>
> (a 1 2 3 4) => 14


Very nice, thanks for this. However, using eval feels a bit too scary even for a crazy guy like myself!

Perhaps I need to take a different angle to solving my problem…

Sam

---
http://sam.aaron.name

Alan Malloy

unread,
Jul 29, 2011, 5:05:35 AM7/29/11
to Clojure
On Jul 29, 1:49 am, Sam Aaron <samaa...@gmail.com> wrote:
> On 29 Jul 2011, at 07:22, Ken Wesson wrote:
>
> > On Fri, Jul 29, 2011 at 12:49 AM, Jeff Rose <ros...@gmail.com> wrote:
> >> I don't think it's very typical to pass a form to a function, unless
> >> you plan on using eval at runtime.
>
> > Or it's a function called by a macro to do some processing of forms.
>
> Yep, this is precisely what I was considering. I should have explicitly made mk-dynamically-bound-fn a macro in my example - a miscommunication on my part.
>
> On 29 Jul 2011, at 02:12, Alan Malloy wrote:
>
>
>
> > It's not clear how much of this is relevant to your actual problem, vs
> > the simple version you're posting here.
>
> Sorry, I was just trying to simplify things to try and get directly to the meat of the problem.

No apologies needed - reducing to a simple case is great. But here,
the simplification was probably too severe.
(let [args (gensym 'args)]
`(fn [& ~args]
(let ~(binding-vec args)
~@forms))))

((magic-fn (+ size 10)) 1 2) ;=> 12

Sam Aaron

unread,
Jul 29, 2011, 5:20:59 AM7/29/11
to clo...@googlegroups.com
Hi Alan,

On 29 Jul 2011, at 10:05, Alan Malloy wrote:
>>
>> Sorry, I was just trying to simplify things to try and get directly to the meat of the problem.
>
> No apologies needed - reducing to a simple case is great. But here,
> the simplification was probably too severe.
>

Sorry, I'm English - apologies are just part of normal discourse ;-)

>
> (defn binding-vec [foos]
> ['size `(count ~foos)])
>
> (defmacro magic-fn
> [& forms]
> (let [args (gensym 'args)]
> `(fn [& ~args]
> (let ~(binding-vec args)
> ~@forms))))
>
> ((magic-fn (+ size 10)) 1 2) ;=> 12
>


Fantastic! I believe this is exactly what I'm looking for. Thank-you so much.

Out of interest, are you able to briefly describe (or able to direct me at some relevant literature that does) why your original solution used ~'args and why this version uses (gensym 'args). These subtle macro compile-time/runtime syntax tricks are something I'm clearly yet to master.

Sam

---
http://sam.aaron.name

Sam Aaron

unread,
Jul 29, 2011, 6:57:01 AM7/29/11
to clo...@googlegroups.com
Hi there,

On 29 Jul 2011, at 10:05, Alan Malloy wrote:
>

> (defn binding-vec [foos]
> ['size `(count ~foos)])
>
> (defmacro magic-fn
> [& forms]
> (let [args (gensym 'args)]
> `(fn [& ~args]
> (let ~(binding-vec args)
> ~@forms))))
>
> ((magic-fn (+ size 10)) 1 2) ;=> 12

Actually, sadly, this still quite doesn't do what I want - and definitely highlights the perils of over-simplifying the question!

I'd like binding-vec to generate the binding vector entirely from foos, i.e. both the binding name and value. Imagine that there's a var names:

(def names ['size 'foo 'bar 'baz])

To continue in the context of the (over-simplified) question, I'd like binding-vec to dynamically generate me a vec of:

['size `(count ~foos)
'foo `(count ~foos)
'bar `(count ~foos)
'baz `(count ~foos)]

However, something like the following doesn't work:

(defn binding-vec [foos]
`(vec (interleave ~names ~(take (count names) (repeat '`(count ~foos))))))

A, because the above code is probably incorrect (I just cobbled it together for the example) and more importantly:
B, because the code doesn't return a vec, it returns code that returns a vec.

I really seem to be getting my brain in a knot over this. :-( Any illumination would be really appreciated


Sam

---
http://sam.aaron.name

Ken Wesson

unread,
Jul 29, 2011, 7:11:32 AM7/29/11
to clo...@googlegroups.com
On Fri, Jul 29, 2011 at 6:57 AM, Sam Aaron <sama...@gmail.com> wrote:
> However, something like the following doesn't work:
>
> (defn binding-vec [foos]
>  `(vec (interleave ~names ~(take (count names) (repeat '`(count ~foos))))))
>
> A, because the above code is probably incorrect (I just cobbled it together for the example) and more importantly:
> B, because the code doesn't return a vec, it returns code that returns a vec.
>
> I really seem to be getting my brain in a knot over this. :-( Any illumination would be really appreciated

Why not just (vec (interleave names (take (count names) (repeat
`(count ~foos)))))?

Of course I assume you eventually want something more complex than
just (count foos) as the value for every variable, or why have more
than one variable?

Sam Aaron

unread,
Jul 29, 2011, 7:35:24 AM7/29/11
to clo...@googlegroups.com

On 29 Jul 2011, at 12:11, Ken Wesson wrote:
>
> Why not just (vec (interleave names (take (count names) (repeat
> `(count ~foos)))))?
>

That seems to blow up:


(defn binding-vec [foos]
(vec (interleave names (take (count names) (repeat `(count ~foos))))))

(defmacro magic-fn
[& forms]
(let [args (gensym 'args)]
`(fn [& ~args]
(let ~(binding-vec args)
~@forms))))

((magic-fn (+ size 10)) 1 2) ;=> 12

> Of course I assume you eventually want something more complex than
> just (count foos) as the value for every variable, or why have more
> than one variable?

Yes, of course :-)

The actual end goal is to be able to define a macro (or whatever) passing in a body containing symbols which are yet to be defined. The macro will then return me function which has variable arity. When I call this function I'd like the values of the args be bound to a specified list of symbols (which happen to be the arg names) so that the original body can execute correctly. The interesting part is that I'd like to be able to specify defaults interleaved with the arg names.

For example:

(def yo (with-arg-params [foo 0 bar 1 baz 2]
(+ foo bar baz)

a would now be bound to a fn which I could call:

(yo 1 2 3) ;=> 6

I could also call it with fewer args than expected:

(yo 1 2) ;=> 5

this works because bad has a default of 2.

There's a bunch of other neat tricks I can do here which would probably only serve to complicate this example, but this is the general gist of things.

I assume for this to work I have to dynamically create a let binding over the original form, so the first call would execute code like:

(let [foo 1 bar 2 baz 3] (+ foo bar baz))

and the second example would execute code like:

(let [foo 1 bar 2 baz 2] (+ foo bar baz)

this would therefore mean that this let statement's vec needs to be a function of the args passed to yo and the initial set of defaults. Unfortunately I'm currently unable to figure out how to achieve this.

Sam

---
http://sam.aaron.name

Ken Wesson

unread,
Jul 29, 2011, 7:56:31 AM7/29/11
to clo...@googlegroups.com
On Fri, Jul 29, 2011 at 7:35 AM, Sam Aaron <sama...@gmail.com> wrote:
>
> On 29 Jul 2011, at 12:11, Ken Wesson wrote:
>>
>> Why not just (vec (interleave names (take (count names) (repeat
>> `(count ~foos)))))?
>>
>
> That seems to blow up

How so?

> The actual end goal is to be able to define a macro (or whatever) passing in a body containing symbols which are yet to be defined. The macro will then return me function which has variable arity. When I call this function I'd like the values of the args be bound to a specified list of symbols (which happen to be the arg names) so that the original body can execute correctly. The interesting part is that I'd like to be able to specify defaults interleaved with the arg names.
>
> For example:
>
> (def yo (with-arg-params [foo 0 bar 1 baz 2]
>    (+ foo bar baz)
>
> a would now be bound to a fn which I could call:
>
> (yo 1 2 3) ;=> 6
>
> I could also call it with fewer args than expected:
>
> (yo 1 2) ;=> 5
>
> this works because bad has a default of 2.

(defmacro with-arg-params [bindings & body]
(let [bindings (apply array-map bindings)]
`(fn
~@(for [i (range (inc (count bindings)))]
`(~(vec (take i (keys bindings)))
(let ~(vec (mapcat identity (drop i bindings)))
~@body))))))

=> (def x (with-arg-params [foo 1 bar 2 baz 3] (+ foo bar baz)))
#'user/x
=> (x 4 5 6)
15
=> (x 4 5)
12
=> (x 4)
9
=> (x)
6

=> (macroexpand-1 '(with-arg-params [foo 1 bar 2 baz 3] (+ foo bar baz)))
(clojure.core/fn ([] (clojure.core/let [foo 1 bar 2 baz 3] (+ foo bar
baz))) ([foo] (clojure.core/let [bar 2 baz 3] (+ foo bar baz))) ([foo
bar] (clojure.core/let [baz 3] (+ foo bar baz))) ([foo bar baz]
(clojure.core/let [] (+ foo bar baz))))

=

(fn
([] (let [foo 1 bar 2 baz 3] (+ foo bar baz)))
([foo] (let [bar 2 baz 3] (+ foo bar baz)))
([foo bar] (let [baz 3] (+ foo bar baz)))
([foo bar baz] (let [] (+ foo bar baz))))

Note that array-map cannot be changed to hash-map here because the
order must be preserved.

Sam Aaron

unread,
Jul 29, 2011, 9:48:12 AM7/29/11
to clo...@googlegroups.com

On 29 Jul 2011, at 12:56, Ken Wesson wrote:
>>>
>>
>> That seems to blow up
>
> How so?

(defn binding-vec [foos]


(vec (interleave names (take (count names) (repeat `(count ~foos))))))

(defmacro magic-fn
[& forms]
(let [args (gensym 'args)]
`(fn [& ~args]
(let ~(binding-vec args)
~@forms))))

((magic-fn (+ size 10)) 1 2) ;=> Unable to resolve symbol: size in this context

>
> (defmacro with-arg-params [bindings & body]
> (let [bindings (apply array-map bindings)]
> `(fn
> ~@(for [i (range (inc (count bindings)))]
> `(~(vec (take i (keys bindings)))
> (let ~(vec (mapcat identity (drop i bindings)))
> ~@body))))))
>
> => (def x (with-arg-params [foo 1 bar 2 baz 3] (+ foo bar baz)))
> #'user/x
> => (x 4 5 6)
> 15
> => (x 4 5)
> 12
> => (x 4)
> 9
> => (x)
> 6

Ah, very neat :-)

However... (apologies, there always seems to be a however from me in this thread!)

This totally satisfies the "general gist" part of my description but doesn't allow for the "bunch of other neat tricks I can do here" part. The behaviour of with-arg-params needs to match the behaviour of other functions in my system for consistency. The other tricks are keyword args and even a combination of keyword args and normal args. I have the majority of code to implement all this behaviour - I can generate the correct vector of bindings given a list of supplied args and some defaults. However, it's the ability to dynamically create a let binding within a function for which the bindings themselves are a function of the actual function's args (and some constant(s)) which is evading me.

This seems the closest I can get to a solution:

(defn binding-vec [foos]
(fn-which-returns-a-vec-of-alternating-symbols-and-vals foos)

(defmacro magic-fn
[& forms]
(let [args (gensym 'args)]
`(fn [& ~args]
(let ~(binding-vec args)
~@forms))))

((magic-fn (+ size 10)) 1 2)

I just don't have the magic skills to get it to actually work :-(

Sam

P.S. Thanks everyone for your help so far. My brain is overheating but I am learning a lot.

---
http://sam.aaron.name

Ken Wesson

unread,
Jul 29, 2011, 5:02:28 PM7/29/11
to clo...@googlegroups.com
> P.S. Thanks everyone for your help so far. My brain is overheating but I am learning a lot.

You're welcome.

To do what you're proposing you will probably need the emitted
function to look like:

(fn [& args]
(let [foo (some logic goes here)
bar (some logic goes here)
...]
(body goes here)))

where the first logic checks the args for containing the value for
foo, if it does evaluates to that value, and if not evaluates to foo's
default; the second does likewise for bar; and so on.

Alternatively, if that might result in a lot of inefficient duplication,

(fn [& args]
(let [argmap__5673__auto (#'some-ns/parse-args args)
foo (or (:foo argmap__5673__auto) 42)
bar (or (:bar argmap__5673__auto) nil)
...]
(body goes here)))

where you define a private parse-args function in your namespace to
turn an argument seq into a map of argument name keywords to values,
with some omissions, and 42 and nil are the defaults which the macro
builds right into the function.

Of course, this has the slight issue that arguments whose values are
false or nil will be changed to their defaults. You may need some
trickier handling, e.g.

(let [...
foo (:foo argmap__5673__auto)
foo (if (:foo argmap__5673__auto) foo 42)
...]
...)

or

(let [...
foo (find argmap__5673__auto :foo)
foo (if foo (val foo) 42)
...]
...)

Sam Aaron

unread,
Jul 30, 2011, 7:29:07 AM7/30/11
to clo...@googlegroups.com
Hi Ken,

On 29 Jul 2011, at 22:02, Ken Wesson wrote:

>> P.S. Thanks everyone for your help so far. My brain is overheating but I am learning a lot.
>
> You're welcome.
>
> To do what you're proposing you will probably need the emitted
> function to look like:
>
> (fn [& args]
> (let [foo (some logic goes here)
> bar (some logic goes here)
> ...]
> (body goes here)))
>
> where the first logic checks the args for containing the value for
> foo, if it does evaluates to that value, and if not evaluates to foo's
> default; the second does likewise for bar; and so on.

I just finished implementing a solution and it looks exactly like this. You're absolutely right - this is precisely the pattern I was looking for.

What I didn't do, and I think the biggest lesson I've learned from these early macro-fighting days, is to do exactly what you propose: sketch out what the macro should expand to *before* working on the macro itself.

Thanks everyone once again,

Sam

---
http://sam.aaron.name

Ken Wesson

unread,
Jul 30, 2011, 8:57:46 PM7/30/11
to clo...@googlegroups.com
On Sat, Jul 30, 2011 at 7:29 AM, Sam Aaron <sama...@gmail.com> wrote:
>> (fn [& args]
>>  (let [foo (some logic goes here)
>>        bar (some logic goes here)
>>        ...]
>>    (body goes here)))
>
> I just finished implementing a solution and it looks exactly like this. You're absolutely right - this is precisely the pattern I was looking for.
>
> What I didn't do, and I think the biggest lesson I've learned from these early macro-fighting days, is to do exactly what you propose: sketch out what the macro should expand to *before* working on the macro itself.

That's generally good advice when macro-writing.

> Thanks everyone once again,

You're welcome.

Alan Malloy

unread,
Jul 30, 2011, 9:07:25 PM7/30/11
to Clojure
On Jul 29, 2:02 pm, Ken Wesson <kwess...@gmail.com> wrote:
> (fn [& args]
>   (let [argmap__5673__auto (#'some-ns/parse-args args)
>         foo (or (:foo argmap__5673__auto) 42)
>         bar (or (:bar argmap__5673__auto) nil)
>         ...]
>     (body goes here)))
> where you define a private parse-args function in your namespace to
> turn an argument seq into a map of argument name keywords to values,
> with some omissions, and 42 and nil are the defaults which the macro
> builds right into the function.
>
> Of course, this has the slight issue that arguments whose values are
> false or nil will be changed to their defaults. You may need some
> trickier handling, e.g.
>
> (let [...
>       foo (:foo argmap__5673__auto)
>       foo (if (:foo argmap__5673__auto) foo 42)
>       ...]
>   ...)

(get :foo argmap__5673__auto 42) is the right way to solve this
problem.

Or if, as in the current example, you want to destructure a map with
lots of defaults, simply:

(let [{:keys [foo bar] :or {foo 42}} (parse-whatever)]
...body...)

Ken Wesson

unread,
Jul 30, 2011, 10:20:29 PM7/30/11
to clo...@googlegroups.com
On Sat, Jul 30, 2011 at 9:07 PM, Alan Malloy <al...@malloys.org> wrote:
> (get :foo argmap__5673__auto 42) is the right way to solve this
> problem.

Is another way to solve it, yes, and a good one.

> Or if, as in the current example, you want to destructure a map with
> lots of defaults, simply:
>
> (let [{:keys [foo bar] :or {foo 42}} (parse-whatever)]
>  ...body...)

Or

(let [{:keys [foo bar]} (merge {:foo 42} (parse-whatever))]
...body...)

The defaults map can be a literal emitted by the macro and must be on
the left of the map-returning (parse-whatever) call.

Reply all
Reply to author
Forward
0 new messages