A version of With that binds variables sequentially

97 views
Skip to first unread message

D. Grady

unread,
Dec 15, 2008, 7:49:19 AM12/15/08
to
Many times when I'm programming, I write code like this:

With[{a1 = 5},
With[{a2 = f[a1]},
With[{a3 = g[a1, a2]},
h[a1, a2, a3]]]]

I wish I could write this instead:

With[{
a1 = 5,
a2 = f[a1],
a3 = g[a1, a2]},
h[a1, a2, a3]]

Although With doesn't work this way, I didn't see any reason that it
shouldn't, so I wrote a function called WithMany. It works by taking
something in the second form above and holding all of the arguments,
expanding out to the first form, and then releasing the hold.

In spite of the simplicity of the idea, it was tricky for me to figure
out the details of this function. I thought that someone else might
find it helpful, and I also had some questions about it.

Am I using a bad idiom with the nested With's? Is it better to just
use Module and be done with it?

Is there a way to get syntax highlighting for WithMany? I found the
SyntaxInformation stuff, but it doesn't seem to expose the
highlighting that's used for With, Module, or Block.

Are there any cases where this function fails or does something
unexpected?

Is there a clearer way to write this function? It seems very
complicated right now, especially in the fact that I need to use both
a special symbol with attribute HoldAll and a special function with
attribute HoldAll. Maybe someone sees a way to make it simpler?

Really I'm just curious what other people think about this. Any
comments would be appreciated.

A simple example:

WithMany[{
a1 = 5,
a2 = RandomChoice@Range@a1,
a3 = f[a2, a1]},
{a1, a2, a3}]

{5, 1, f[1, 5]}

The definition:

ClearAll[WithMany];
SetAttributes[WithMany, HoldAll];
WithMany[args_, body_] :=
Module[{heldArgs, f, W, structure},
(* The very first thing that has to happen is to put args into a
function with a hold attribute like HoldAll. Doing anything else
means that args will be evaluated. *)

heldArgs = Hold[args];

(* args is coming to us as a list of bindings. We want to replace
the List with Hold. We can do this because Hold is wrapping
everything right now. *)

heldArgs = Apply[Hold, heldArgs, {1}];

(* Now we want to take the outermost Hold away so that we're left
with Hold[bind1,bind2,bind3,...]. *)
heldArgs = ReleaseHold[heldArgs];

(* Now we want to put each binding inside a List because With
requires that syntax. *)
heldArgs = Map[List, heldArgs];

(* Set up a symbol that will serve as a non-evaluating wrapper. *)

SetAttributes[W, HoldAll];

(* Next define a function to use in the Fold operation. This
function needs to have the attribute HoldAll, otherwise it will
evaluate its arguments prematurely. *)
f = Function[
{bod, binding},
W[binding, bod],
{HoldAll}];

(* Use f to Fold W onto the bindings. This will create the correct
structure without evaluating anything. *)

(* If we had used With directly instead of W, then as soon as the
first With statement is completely formed it will be evaluated,
leaving the more outer With statements useless. *)

(* Wrapping body in Unevaluated is neccessary to prevent body from
being evaluated as soon as it appears in the arguments of Fold. *)

structure = Fold[f, Unevaluated[body], Reverse@heldArgs];

(* The final step is to replace all of the W symbols with With.
Replacement still works inside of Hold, and the symbol W is local to
this module so there's no danger of messing up the input. *)
structure /. W -> With
]

dh

unread,
Dec 16, 2008, 6:03:59 AM12/16/08
to

Hi,

"With" only allowes local constants. "Module" not only allowes local

constants, but also local variables. This easily solves your problem:

Module[{a1=5,a2,a3},a2=f[a1];a3=g[a1,a2];h[a1,a2,a3]]

hope this helps, Daniel

rych

unread,
Dec 18, 2008, 7:23:20 AM12/18/08
to
On Dec 15, 12:49 pm, "D. Grady" <D.C.Gr...@gmail.com> wrote:
> Many times when I'm programming, I write code like this:
>
> With[{a1 = 5},
> With[{a2 = f[a1]},
> With[{a3 = g[a1, a2]},
> h[a1, a2, a3]]]]
>
> I wish I could write this instead:
>
> With[{
> a1 = 5,
> a2 = f[a1],
> a3 = g[a1, a2]},
> h[a1, a2, a3]]
>
> Although With doesn't work this way, I didn't see any reason that it
> shouldn't, so I wrote a function called WithMany. It works by taking
> something in the second form above and holding all of the arguments,
> expanding out to the first form, and then releasing the hold.
>

I've done something similar in the past. Here is my version:
Off[With::"lvlist"]
Off[Module::"lvlist"]
ClearAll[WithWith, WithModule]
SetAttributes[{WithWith, WithModule}, HoldAll]
WithWith[x__, expr_] :=
ReleaseHold@
Fold[With[#2, #1] &, Hold[expr],
Reverse@Map[Hold, Unevaluated@{x}] /.
Hold[h_[y__]] :> Hold[{h[y]}] /; h =!= List]
WithModule[x__, expr_] :=
WithWith[xh =
Reverse@Map[Hold, Unevaluated@{x}] /.
Hold[h_[y__]] :> Hold[{h[y]}] /; h =!= List,
xh1 = First@xh,
ReleaseHold@Fold[With[#2, #1] &, Module[xh1, Hold[expr]], Rest@xh]
]

I think if I spend some time deciphering it, it's going to look the
same. (I must never never never write undocumented "one-liners")
Igor


D. Grady

unread,
Dec 25, 2008, 3:56:12 AM12/25/08
to
Hi Igor, I spent some time fiddling with your code and agree, both of
our solutions do basically the same thing. I think the biggest
difference is that you prevent evaluation until the end by wrapping
things in Hold, and I use a custom symbol with the attribute HoldAll.
I like how your version lets you make the nesting explicit when you
call the function; that's definitely not something I would have
thought of.

I thought about my original version some more and came up with a
shorter solution that uses a recursive function instead of Fold. This
one seems the easiest to understand and I like it for that, but it
doesn't give the user explicit control over the nesting.

Well anyhow, it's an interesting problem. I feel like I've got a
better understanding of exactly how evaluation works now, if nothing
else. Thanks for sharing your solution, and happy holidays!

-Daniel

ClearAll[WithNest];
SetAttributes[WithNest,HoldAll];

(* The user-facing function call. Takes a list of bindings and a
body, just like With. *)
WithNest[bindings_List,body_]:=WithNest[bindings,Hold[body],True];

(* This is the base case of the recursive part of the definition.
When we run out of bindings, it's time to evaluate the result. *)
WithNest[{},Hold[body_],True]:=body;

(* This is the recursive part of the definition. *)
WithNest[{bindings___,a_},Hold[body_],recursing:(True|False)]:=
WithNest[
{bindings},
Hold[With[List@a,body]],
True];

rych

unread,
Dec 27, 2008, 7:03:57 AM12/27/08
to
Thanks, Daniel. I believe you've exhausted this topic now :) In fact I
don't even fully comprehend my versions anymore. I'm going to use
these functions as if they were built in, especially since you've
validated them once again. Good exercise indeed. For completeness, I
give the link to another topic where I introduced my functions:
"Iterative constants and variables definitions" [
http://groups.google.com/group/comp.soft-sys.math.mathematica/browse_thread/thread/4f51ab8b33dbb689/
].
Merry Christmas!
Igor

Reply all
Reply to author
Forward
0 new messages