# Half-delayed evaluation

In a certain wider context, I need to make some delayed definitions, such as x := a + b + c, but within these definitions I want a and b to be evaluated to their current values, not their later values. Here's a bit of code that does the trick:

a = 1; b = 2; c = 5;
With[{a = a, b = b}, x := a + b + c; y := b + c];
a = 10; b = 20; c = 50;


If x is printed at the end, it comes out to be 53 and if its definition is inspected with ?x, it comes out to be x := 1 + 2 + c. Likewise at the end y is y := 2 + c.

This is precisely the behaviour I want, but within my wider context, I care about elegance and this is inelegant. So I'd like to be able to define a command (really, a "macro") WithCurrent, so that WithCurrent[{a, b}, delayeddefs] would be equivalent to With[{a = a, b = b}, delayeddefs].

Any ideas?

-

Here is one way to define such a macro:

SetAttributes[withCurrent, HoldAll]
withCurrent[{v:(_Symbol...)}, body_] :=
Replace[Hold[v], s_ :> (s = s), {1}] /. _[a___] :> With[{a}, body]


withCurrent is given the HoldAll attribute to delay the evaluation of the arguments until after we have had a chance to process them. It wraps the supplied sequence of symbols in Hold and then uses Replace to convert each symbol into a "self-assignment". The resulting held assignments are then transformed into the desired With statement by means of a replacement rule.

Example:

a = 1; b = 2; c = 5;
withCurrent[{a, b}, x := a + b + c; y := b + c];
a = 10; b = 20; c = 50;

?x
(* x := 1 + 2 + c *)

?y
(* y := 2 + c *)

x
(* 53 *)

y
(* 52 *)


If desired, we can add a definition to withCurrent that causes it to fail noisily if it is called with invalid arguments:

w:withCurrent[___] := (Message[withCurrent::malformed, Short@HoldForm[w]]; Abort[])
withCurrent::malformed = "Malformed use of withCurrent: ";


Example:

withCurrent[{a, b, c = 3}, a + b]
(*
withCurrent::malformed: Malformed use of withCurrent: withCurrent[{a,b,c=3},a+b]
\$Aborted
*)


This is usually a good defensive-programming measure when defining macros. Otherwise, malformed usage in larger programs might cause strange results that are not detected until long after the macro has been called.

-

The key is to use the injection pattern.

SetAttributes[WithCurrent, HoldAll]
WithCurrent[list_, delayeddefs___] :=
With[{ls =
Replace[Map[Hold, Replace[Hold@list, Hold[{x___}] :> Hold[x]],
1], Hold[x_] :> (x = x), -1]},
Hold[With[ls, delayeddefs]] /. Hold[x___Set] :> {x}] // ReleaseHold
a = 1;
b = 2;
c = 3;
WithCurrent[{a, b}, x := a + b + c]
?x
(* x:=1+2+c *)


One minor issue I see is that last ReplaceAll may be too strong - you don't normally see Hold[___Set] patterns in regular expressions but if they appear anywhere else they'll get replaced. Also, any Hold values in delayeddefs will get released. This shouldn't be a problem if delayeddefs consists of delayed definitions as the name suggests.

-