19

How can we define a function that works like f[x_]=ComputeSomething[x] and treats x as a variable that does not have a value? We could call this function LocalSet and the computation should be done when the assignment is made as in the following example.

var=3;
LocalSet[f[var_],Normal[Series[Exp[var],{var,0,3}]]];
DownValues[f]

(*
---> {HoldPattern[f[var_]]:>1+var+var^2/2+var^3/6}
*)

var
(*
---> 3
*) 

Notice I don't want to be limited to a pattern variable (x_). The function called LocalSet should figure out what symbols are used for patterns and evalute the right side with those variables in a Block construct.

Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
Ted Ersek
  • 7,124
  • 19
  • 41

1 Answers1

16

Perhaps something like this:

SetAttributes[localSet, HoldAll]

localSet[lhs_, rhs_] := 
  Union @@ Cases[
    Unevaluated[lhs], 
    Verbatim[Pattern][p_, _] :> HoldComplete[p],
    Infinity,
    Heads -> True
  ] /. _[x___] :> Block[{x}, lhs = rhs;]

Test:

var=3;
localSet[f[var_],Normal[Series[Exp[var],{var,0,3}]]]
DownValues[f]
{HoldPattern[f[var_]] :> 1 + var + var^2/2 + var^3/6}
Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
  • I came up with almost exactly the same code. Hope you don't mind my edit to make it work in more general cases and avoid returning a value. – Szabolcs Feb 19 '12 at 09:40
  • 2
    It not only about returning a value. It's about evaluating the expression, which might have side effects or unintended consequences. Consider f[x_?NumericQ] := (Print["numeric"]; x); n = 5; localSet[g[n_], f[n]], which has different behaviour when the semicolon is omitted. I think it's important here that the expression not be evaluated with n having a value---there's no telling what that might lead to. – Szabolcs Feb 19 '12 at 12:12
  • 1
    +1. I would write the same code, except including Heads->True option in Cases, if you want to cover SubValues. Right now an attempt to assign to say f[var_][1] will produce an error. If you want to be totally safe, I'd also use HoldComplete in place of Hold. – Leonid Shifrin Feb 19 '12 at 13:54
  • @Leonid, thank you, I will add Heads -> True. Regarding HoldComplete I hesitate to use it unless I know it is needed because there are times it is not wanted. If you have a different opinion you know I would like to hear it. – Mr.Wizard Feb 19 '12 at 14:04
  • @Mr.Wizard Well, imagine someone is crazy or careless enough to execute this at some point: ClearAll[var];var /: _[var] := var; var = 3; The current version of localSet won't work then. This is of course a rather contrived example, but if we want to make things robust, any example should work. For this case, I don't see how HoldComplete can bring any unwanted effects, so I'd use it. – Leonid Shifrin Feb 19 '12 at 14:13
  • @Leonid to confirm: {HoldAll -> HoldAllComplete, Hold -> HoldComplete} correct? – Mr.Wizard Feb 19 '12 at 15:43
  • @Mr.Wizard Actually, HoldAll may be enough, so just Hold-> HoldComplete I think. – Leonid Shifrin Feb 19 '12 at 20:50
  • 1
    @Mr.Wizard The possible troublesome symbols will be deeper than level one during the call to localSet[f[...],rhs], because they will be inside f. So, as long as localSet is HoldAll, you don't have to care about them, exactly due to the depth-1 limitation for UpValues search. But when you destructure, you have Hold[sym], and here HoldComplete is essential. – Leonid Shifrin Feb 19 '12 at 20:56
  • 1
    Well, to make it a little bit moore robust it would be better to use a preorder ReplaceAll based Cases that stops inside Verbatims. Something along the lines of Module[{tag},Reap[Unevaluated[lhs]/.{i_Verbatim:>i, Verbatim[Pattern][p_, _]/;Sow[HoldComplete[p], tag]:>Null}, tag][[1, -1]]] – Rojo Jul 19 '13 at 17:24
  • @Rojo Why am I only reading this now? (The notice system is unreliable, that's why.) Thanks for the suggestion. I'll have to think that one through, then add it to the answer. – Mr.Wizard Jul 31 '13 at 18:39
  • @Mr.Wizard it is very unreliable, I miss pings a lot. – Rojo Jul 31 '13 at 18:46