5
$\begingroup$

Consider the following code.

f[a_,b_]:=x
x=a+b;
f[1,2]
(* a + b *)

From a certain viewpoint, one might expect it to return 3 instead of a + b: the symbols a and b are defined during the evaluation of f and a+b should evaluate to their sum.

Why is this viewpoint wrong? What's the right way to make it behave the way I want it to? (Something more clever than f[p_,q_]:=x/.{a->p,b->q};.)

$\endgroup$
2
  • 4
    $\begingroup$ This viewpoint is wrong. Read this, and this. $\endgroup$ Dec 24, 2014 at 1:05
  • $\begingroup$ Closely related, possible duplicate: (11461) $\endgroup$
    – Mr.Wizard
    Feb 2, 2015 at 15:39

4 Answers 4

10
$\begingroup$

Analysis

In Mathematica when a definition is applied the expressions (arguments) that match pattern objects on the left-hand-side (LHS) are substituted into the matching right-hand-side (RHS) names before it is evaluated. This is separate from the evaluation that does or does not take place at the time the rule or definition is created. This substitution-before-evaluation is invariant between Set and SetDelayed.

Please consider this example:

ClearAll[f, a, b, x]

f[a_, b_] = (Print["one: ", a, b]; x);

x := Print["two: ", a, b];

f[1, 2]

{a, b} = {3, 4};

f[1, 2]

one: ab

two: ab

two: 34

  • The use of Set in the definition creation causes the RHS to evaluate, printing "one:", but this Print is not part of the definition created as it does not remain in the evaluated form of (Print["one: ", a, b]; x).

  • When the definition is used no a or b appear in the explicit unevaluated RHS (x) therefore no substutions are made. The RHS is then evaluated and the Print statement in the global definition of x fires.

  • Between the first and second time the function is used a and b are given global values. When the function is next called the same evaluation sequence takes place but when x evaluates to Print["two: ", a, b] this time a and b evaluate to 3 and 4.


Alternatives

You asked for "something more clever than f[p_,q_]:=x/.{a->p,b->q};" but that may in essence be what you need. This code effectively performs the substitution after the RHS evaluation each time the definition is applied. If you want to use the argument values during evaluation you will need to use Block or another function that works like Block. Here is an example:

ClearAll[f, a, b, x]
f[aa_, bb_] := Block[{a = aa, b = bb}, x]

x = a + b;
f[1, 2]

x = 3 a + b^2;
f[1, 2]
3

7

All that remains is to make construction of these definitions easier which we can do with a little meta-programming. Here is one method:

SetAttributes[blockSet, HoldFirst]

blockSet[LHS_ := RHS_] :=
 With[{subs = 
    Cases[Unevaluated[LHS], 
     Verbatim[Pattern][p_, x_] :> Append[Hold[p], Module[{p}, p]], -2]},
  {
    ReplaceAll[Hold[LHS], HoldPattern[#] :> #2 & @@@ subs],
    Set @@@ Hold @@ subs
  } /. {Hold[newLHS_], Hold[sets__]} :> (newLHS := Block[{sets}, RHS])
 ]

Now:

Remove[f, a, b, x]

blockSet[
  f[a_, b_] := x
]

?f
Global`f

f[a$752_, b$753_] := Block[{a = a$752, b = b$753}, x]

And:

x = a + b;
f[1, 2]
3
$\endgroup$
1
  • $\begingroup$ One issue with this approach is that the user might like to use the definition of x at the time blockSet is evaluated, instead of at the time f is evaluated. $\endgroup$
    – Carl Woll
    Oct 27, 2018 at 19:02
3
$\begingroup$

I gave the following answer to essentially the same question:

TagSetDelayed[x, lhs_, rhs_] ^:= SetDelayed @@ (Hold[lhs, rhs] /. OwnValues[x])

For your example:

x = a + b;
x /: f[a_, b_] := x

Then:

f[1, 2]

3

The main difference between this answer and @Mr.Wizard's answer is the time at which x is evaluated. In my answer, the OwnValues of x at the time of function definition is used. In @Mr.Wizard's answer, the OwnValues of x at the time of function application is used. Let's see an example where this difference manifests:

x = a + b;
x /: f[a_, b_] := x

The DownValues of f are:

DownValues[f]

{HoldPattern[f[a_, b_]] :> a + b}

Notice that no trace of x remains. If I change x to something else, and then evaluate f:

x = 1;
f[1, 2]

3

The function f does not change when x is changed. Now, for @Mr.Wizard's answer:

x = a + b;
blockSet[f[a_, b_] := x]

Notice that the OwnValues of x have not been used in the definition of f:

DownValues[f]

{HoldPattern[f[a$29662_, b$29663_]] :> Block[{a = a$29662, b = b$29663}, x]}

If I change x:

x = 1;
f[1, 2]

x = a-b;
f[1, 2]

x = 22;
f[1, 2]

1

-1

22

Notice how the definition of f is dependent on the value of x. This difference in behavior should be considered when choosing which approach to take.

$\endgroup$
1
$\begingroup$

check :

f[1,2]//Trace

You will see that 1 & 2 are passed first before replacing x with its value.

If you want to get your result then use Set not SetDelayed

Clear[a,b];
x=a+b;   
f[a_,b_]=x;
f[1,2]

(*3*)
$\endgroup$
2
  • 1
    $\begingroup$ Algohi your second example caught me off guard until I took a second look. It may not work in the way that you think it does. The use of Set does not cause the right-hand-side of the definition to be (re)evaluated before substitution at the time the function is used. Rather it only causes RHS evaluation at the time the definition is constructed. If you start with Clear[x] you will see that the result is a + b just as it was in the original example. (@Igor note this too.) $\endgroup$
    – Mr.Wizard
    Dec 24, 2014 at 1:52
  • $\begingroup$ @Mr.Wizard absolutely correct. correction is added. $\endgroup$ Dec 24, 2014 at 2:15
1
$\begingroup$

One somewhat organized way to get what you want is to be very explicit about which expressions are functions and which are values. For example, your x is really a function of a and b, but you are writing x=a+b. If instead, you make the functional relationships explicit, then there is less chance of confusion. In the simplest case:

x[a_, b_] := a + b;
f[a_, b_] := x[a, b];
f[1, 2]

Now it is clear why f[1,2] returns 3.

$\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.