How to avoid nested With[]?

With[
{v1 = #},
With[
{v2 = f[v1]},
g[v1, v2]
]
]


How to avoid nested With[] like the above? I'd like to use v1 and v2=f[v1] in the module's body. Is using Module[{v1, v2}, v2=f[v1]; g[v1, v2]] the best/only way to avoid nested module?

-
In Mathematica 10, you can do it with Inactivate, see example here. – István Zachar Jul 10 '14 at 12:08

I don't think one can avoid the need for nested With altogether - I find it a very common case to need declared variables use previously declared variables.

Since I once wrote the function (actually macro) that automates nesting With, and generates nested With at run-time, this is a good opportunity to (re)post it as an answer to an exact question that it actually addresses. I will partly borrow the discussion from this answer.

Implementation

Edit Aug.3, 2015 - added RuleDelayed UpValue, per @Federico's suggestion

Here is the code for it (with added local-variable highlighting):

ClearAll[LetL];
SetAttributes[LetL, HoldAll];
SyntaxInformation[LetL] = {
"ArgumentsPattern" -> {_, _},
"LocalVariables" -> {"Solve", {1, Infinity}}
};
LetL /: (assign : SetDelayed | RuleDelayed)[
lhs_,rhs : HoldPattern[LetL[{__}, _]]
] :=
Block[{With},
Attributes[With] = {HoldAll};
assign[lhs, Evaluate[rhs]]
];
LetL[{}, expr_] := expr;
Block[{With}, Attributes[With] = {HoldAll};


What it does is to first expand into a nested With, and only then allow the expanded construct to evaluate. It also has a special behavior when used on the r.h.s. of function definitions performed with SetDelayed.

I find this macro interesting for many reasons, in particular because it uses a number of interesting techniques together to achieve its goals (UpValues, Block trick, recursion, Hold-attributes and other tools of evaluation control, some interesting pattern-matching constructs).

Simple usage

First consider simple use cases such as this:

LetL[{a=1,b=a+1,c=a+b+2},{a,b,c}]

{1,2,5}


We can trace the execution to see how LetL expands into nested With:

Trace[LetL[{a=1,b=a+1},{a,b}],_With]

{{{{With[{b=a+1},{a,b}]},With[{a=1},With[{b=a+1},{a,b}]]},
With[{a=1},With[{b=a+1},{a,b}]]},
With[{a=1},With[{b=a+1},{a,b}]],With[{b$=1+1},{1,b$}]}


Definition-time expansion in function's definitions

When LetL is used to define a function (global rule) via SetDelayed, it expands not at run-time, but at definition-time, having overloaded SetDelayed via UpValues. This is essential to be able to have conditional global rules with variables shared between the body and the condition semantics. For a more detailed discussion of this issue see the linked above answer, here I will just provide an example:

Clear[ff];
ff[x_,y_]:= LetL[{xl=x,yl=y+xl+1},xl^2+yl^2/;(xl+yl<15)];
ff[x_,y_]:=x+y;


We can now check the definitions of ff:

?ff

Globalff
ff[x_,y_]:=With[{xl=x},With[{yl=y+xl+1},xl^2+yl^2/;xl+yl<15]]

ff[x_,y_]:=x+y


Now, here is why it was important to expand at definition time: had LetL always expanded at run time, and the above two definitions would be considered the same by the system during definition time (variable-binding time), because the conditional form of With (also that of Module and Block) is hard-wired into the system; inside any other head, Condition has no special meaning to the system. The above-mentioned answer shows what happens with a version of Let that expands at run time: the second definition simply replaces the first.

Remarks

I believe that LetL fully implements the semantics of nested With, including conditional rules using With. This is so simply because it always fully expands before execution, as if we wrote those nested With constructs by hand. In this sense, it is closer to true macros, as they are present in e.g. Lisp.

I have used LetL in a lot of my own applications and it never let me down. From my answers on SE, its most notable presence is in this answer, where it is used a lot and those uses illustrate its utility well.

-
Out of curiosity, why did you call it "LetL" ? – QuantumDot Jul 26 '14 at 21:14
@QuantumDot The Let part comes from an analogy with Lisp, where there is a similar function. The L stands for Leonid, because originally there was a mathgroup thread with different implementations, and I had to somehow differentiate mine from the others. – Leonid Shifrin Jul 27 '14 at 10:08
@QuantumDot I call it WithAll – shrx May 19 '15 at 13:02
Why wrap Verbatim around SetDelayed? – bdforbes Jun 23 '15 at 2:05
I suggest the variant LetL /: (assign : SetDelayed | RuleDelayed)[lhs_, rhs : HoldPattern[LetL[{__}, _]]] := Block[{With}, Attributes[With] = {HoldAll}; assign[lhs, Evaluate[rhs]]];, so that one can also use it with RuleDelayed as in Range[10] /. x_Integer :> LetL[{y = x}, -y /; y <= 5]. – Federico Aug 2 '15 at 23:32

Perhaps this will work:

SetAttributes[BetterWith, HoldAll]
BetterWith[{x_}, expr_] := With[{x}, expr];
BetterWith[{x_, rest__}, expr_] := BetterWith[{x}, BetterWith[{rest}, expr]]
BetterWith[{s:Verbatim[Set][x_List, y_], rest___}, expr_] := Quiet @ With[
{x2 = Replace[Hold[x], z_Symbol :> Pattern[z, _], {2}]},
Replace[y, {Apply[HoldPattern,x2] :> BetterWith[{rest}, expr], _ -> $Failed}] ] BetterWith[{y=x+1,x=1},y] (* gives 2 *)  - Just hide the nested With[] under hood of a new symbol? – qazwsx Sep 11 '12 at 4:37 @MaThEmAtika Exactly. – M.R. Sep 11 '12 at 4:42 With works by performing a substitution operation prior to executing its body, and likely it is only a single pass. So, inter-referencing the variables is not possible. Since With accepts the use of SetDelayed (:=), you might think that that could be used, instead. For example, With[{v1 = #, v2 := f[v1]}, g[v1, v2]]& @ p (* g[p, f[v1]] *)  which reveals the other use of With: localization. The v1 in f[v1] is not the same as the v1 used by With, so that method is out, also. The same problem exists for Module as it uses a similar form of localization. However, Block works Block[{v1 = #, v2 = f[v1]}, g[v1, v2]]& @ p (* g[p, f[p]] *)  even though the syntax highlighting makes it appear that the v1 inside f is not localized. But, Block has the attribute HoldAll, so v2 = f[v1] is not executed until v1 has taken on its local definition, and, unlike in With and Module, it is not internally treated with Unique[v1]. - With the potential effects of these different attributes on the program unknown, I think I'd better just use Module[{v1, v2}, v2=f[v1]; g[v1, v2]]. Thanks! – qazwsx Sep 11 '12 at 4:39 +1 for some good points. However, my comment here is that Block solves an entirely different problem. With is unique in that it creates referentially-transparent code (no assignments to its "variables" in the body of With is possible), while Block allows the body to arbitrarily modify the variables it localizes. Another big difference: (nested) With can inject into held expressions, something Block or Module can not. So, I don't really view a replacement of (nested) With with Block or Module as a generally valid solution to the posed problem. – Leonid Shifrin Sep 11 '12 at 12:19 @LeonidShifrin absolutely. With because of its method of operation does things Block cannot do, and vice versa. But, the self-referential part, I thought, was essential to the problem. As long as the other parts of the operation of With are not needed, then Block should be a viable alternative. However, it is not a direct replacement. – rcollyer Sep 11 '12 at 12:43 @rcollyer Well, as long as the solution removes some defining properties of With, I would not consider it a general solution for With specifically. I would agree with you more (in that self-referential part is the main issue) if this was a question of nested Block or Module. – Leonid Shifrin Sep 11 '12 at 12:54 @LeonidShifrin except that it is well known that you can do Scope[{x,y}, x = f[y]] with either Module or Block, so I don't think that question would come up. Personally, I think the mutability of the variables in Block is the biggest issue with using as a replacement for With. The ability to use With to inject code into held expressions is less well known. But, for the OPs problem, it was the perfect fit, provided of course the representative example doesn't stray to far from the actual code. :) – rcollyer Sep 11 '12 at 13:09 In V10.4 or earlier This functionality has snuck into With (ref: Daniel's comment). Note the use of the braces. With[{v1 = #}, {v2 = f[v1]}, g[v1, v2]] (* g[#1, f[#1]] *)  The syntax coloring has not caught up yet: In V10 -- Needs["GeneralUtilities"]; ?GeneralUtilitiesWhere  Where[ass1, ass2, ..., expr] is a version of With that supports multiple sequential assignments. Needs["GeneralUtilities"]; Where[v1 = #, v2 = f[v1], g[v1, v2]] (* g[#1, f[#1]] *) Where[x = 2, t = x^2, Hold[x + t]] (* Hold[2 + 4] *)  - This reminds me of a Q&A I've been meaning to post for years but I never get around to. The basic code behind Where is one of the methods I was using. Maybe I'll finally get around to that soon. (+1 of course.) – Mr.Wizard Jul 15 '14 at 6:33 I can't get Where to work in 10.4... am I alone? – Mehrdad Mar 26 at 11:01 @Mehrdad Likewise; see my answer for the old definition. – Ronald Monson Apr 10 at 1:33 Let's create nested With with knowledge about RawBoxes. I'd not use this method in this case, due to the performance but it is a good exercise: SetAttributes[myWith, HoldAll]; myWith[{spec___}, body_] := ToExpression @@ Fold[ RawBoxes @ MakeBoxes[With[{#2}, #]] &, RawBoxes @ MakeBoxes @ body, RawBoxes /@ MakeBoxes /@ Unevaluated[{spec}] // Reverse ]  It is handy to nest with RawBoxes but we have to get rid of the very outer RawBoxes at the end. Notice that I'm doing this with ToExpression @@ which also make expression from what is left: a = b = 4; myWith[{a = 1, b = a + 1}, {a + b, Hold[a, b]}] a b  {3, Hold[1, 2]} 4 4  - As of 10.4 Where appears to no longer reside in "GeneralUtilities'" however I quite like its form so here is the <= 10.3 Definition (v. similar to M.R.'s answer) that can be placed in an init.m file. SetAttributes[Where, HoldAll]; Where[s : Verbatim[Set][x_List, y_], rest___, expr_] := With[{x2 = Quiet[Replace[Hold[x], z_Symbol :> z_, {2}]]}, Replace[y, {HoldPattern @@ x2 :> Where[rest, expr], _ ->$Failed}]]

Where[expr_] := expr

Where[x_List, expr_] := With[x, expr]

Where[x_, expr_] := With[{x}, expr]

Where[x_, rest__, expr_] := Where[x, Where[rest, expr]]


Note that this slightly improves the previous "GeneralUtilities'" definition by giving effect to a "multiple set" presumably originally intended by the first Where[s:Verbatim[Set] ... definition:

Where[
{x, y} = {1, 3 + x},
z = 2,
{x, y, z}]

(* {1, 3 + x, 2} *)


whereas

In 10.3.1

Needs["GeneralUtilities"];

Where[
{x, y} = {1, 3 + x},
z = 2,
{x, y, z}]

(*
With::lvset: Local variable specification {{x,y}={1,3+x}} contains {x,y}={1,3+x}, which is an assignment to {x,y}; only assignments to symbols are allowed. >>

With[{{x, y} = {1, 3 + x}}, Where[z = 2, {x, y, z}]]

*)
`
-