I often find myself writing code that looks a bit like this:
f[x_Integer] :=
With[
{
range = Range[2] + x
},
With[
{
a = range[[1]],
b = range[[2]],
c = g[range]
},
h[a,b,c]
]
];
It would be nice if I could avoid With
s and just write
f[x_Integer] :=
Let[
range = Range[2] + x,
{a,b} = range,
c = g[range]
,
h[a,b,c]
];
which would then automatically expand to the above at definition time.
What I'm asking is a bit similar to this question. There are additional requirements however. The new scoping construct (Let
in the above) should:
- Group sequential disjoint assignments into single
With
s. - Thread over
List
assignments.
Of course, it should not evaluate the left-hand-sides and the right-hand-sides of the assignments while expanding to With
s.
Any proposals for such a scoping construct? (I'll post my version soon).
Answer
With this helper function:
SetAttributes[partThread, HoldAll];
partThread[l___, rhs_] :=
Join @@ Replace[
MapIndexed[Append[#, First@#2] &, Thread[Hold[{l}]]],
Hold[s_, i_] :> Hold[s = rhs[[i]]],
{1}];
The following modification of LetL seems to work according to your specs:
ClearAll[Let, let];
SetAttributes[{Let, let}, HoldAll];
Let /: Verbatim[SetDelayed][lhs_, rhs : HoldPattern[Let[__, _]]] :=
Block[{With}, Attributes[With] = {HoldAll};
lhs := Evaluate[rhs /. HoldPattern[With[{}, b_]] :> b]
];
Let[args___, body_] := let[{args}, body, {}, {}];
let[{}, body_, {}, _] := With[{}, body];
let[{Set[{s___}, rhs_], rest___}, body_, dec_, syms_] :=
Module[{temp},
partThread[s, temp] /. Hold[d___] :>
let[{temp = rhs, d, rest}, body, dec, syms]
];
let[
{Set[sym_, rhs_], rest___},
body_,
{decs___},
{syms___}
] /; FreeQ[Unevaluated[rhs], Alternatives[syms]] :=
let[{rest}, body, {decs, sym = rhs}, {syms, HoldPattern[sym]}];
let[{args___}, body_, {decs__}, _] :=
Block[{With},
Attributes[With] = {HoldAll};
With[{decs},Evaluate[let[{args}, body, {}, {}]]]
];
This works quite similarly to LetL. What it does in addition to LetL
is that it collects previous declarations into auxiliary lists stored as extra arguments of let
, so that it can group together disjoint declarations. It also threads over arguments, using the partThread
helper function. In all other respects it is the same code as LetL
.
Here is your example:
f[x_Integer] :=
Let[range = Range[2] + x, {a, b} = range, c = g[range], h[a, b, c]];
we can check what was generated:
?f
Global`f
f[x_Integer]:=
With[{range=Range[2]+x},
With[{a=range[[1]],b=range[[2]],c=g[range]},h[a,b,c]]]
Comments
Post a Comment