9
$\begingroup$

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?

$\endgroup$

2 Answers 2

8
$\begingroup$

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.

$\endgroup$
4
$\begingroup$

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.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.