# Arguments to If[] are not evaluated

I got bitten by the following:

f[x_] := 3*x;
g[x_] := If[Log[f[x]] < 0, f[x], 0];
g[x]

Out[11]= If[Log[3 x] < 0, f[x], 0]


where I thought the call to f in the second argument to If would be “expanded”, i.e. replaced by its expression. Once I realized this was the root cause of my issue, I could fix it by inserting Evaluate:

h[x_] := If[Log[f[x]] < 0, Evaluate@f[x], 0];
h[x]

Out[13]= If[Log[3 x] < 0, 3 x, 0]


However, I don't understand why f is expanded in the first argument to If, and not in the second. Moreover, how can I know from the documentation that this will be the case? (so that I can check other functions in the future)

-
I briefly discussed this in the context of Hold- attributes here –  Leonid Shifrin Mar 23 '12 at 9:45

You can evaluate

?? If


to see that its attributes are

Attributes[If]={HoldRest,Protected}


HoldRest tells you that the first argument always gets evaluated while the rest (2nd, 3rd, and 4th) are unevaluated. In practice you can't make any assumptions about the rest because it's not possible to tell how a function evaluated held arguments internally.

That said, if you think about it, it's clear that If must evaluate the first argument, so see if it's True or False. It is also highly desirable not to evaluate the rest of its arguments. How would you expect the following to work?

If[a > 0,  b += 1]


Of course it must only add 1 to b if a > 0, and not otherwise! As you can see, it is a must for any code with side effects not to evaluate automatically. Even if we don't have non-functional constructs, we may have something like

If[a > 0, f[a], f[-a]]


for a function f that would give an error for negative arguments. Finally, if this function f is expensive to evaluate, an If without HoldRest would evaluate it twice, while using the result from only one evaluation---this is wasteful.

I think this should make it clear why it is highly desirable for If to have HoldRest (and also why it is not necessary for it to have HoldAll)

-
Great, thanks! Another part of the question would be: why? Why does it make sense for If that “all but the first argument to a function are to be maintained in an unevaluated form”? –  F'x Mar 23 '12 at 8:05
@F'x See my update. –  Szabolcs Mar 23 '12 at 8:15
+1. It is actually not required that If is HoldRest - could be HoldAll just as well. With If as it is, If does not evaluate the first argument, because it gets evaluated before passed to If. Were If HoldAll, then yes, it would have to evaluate the condition as a part of its semantics (somewhere inside its implementation). –  Leonid Shifrin Mar 23 '12 at 9:05
@Szabolcs thanks for the update, it’s a very clear answer! –  F'x Mar 23 '12 at 9:37
I'd also add to your arguments that since code generally contains side effects, evaluating both If branches would be a disaster just from this point of view (in addition to the points you made). –  Leonid Shifrin Mar 23 '12 at 10:01

One of the best ways to visualize some built-in function is to emulate it. Since the native paradigm of Mathematica is rewrite rules, I find it instructive to see how If can be emulated with them. Here is one possibility:

ClearAll[myIf];
SetAttributes[myIf, HoldAll];
myIf[cond_, iftrue_, iffalse_] :=
With[{result = Replace[cond, {True :> iftrue, False :> iffalse, _ :> None}]},
result /; result =!= None];


This has the same semantics as 3-argument built-in If, except that evaluation of condition is relegated to the implementation of myIf (just for a change - I could just as well set HoldRest for myIf). Now:

myIf[2 > 1, Print[2], Print[1]]


2

myIf[2 > 1, Print[1], Print[2]]


1

and when the condition does not evaluate to True or False, myIf evaluates to itself:

myIf[a > b, Print[a], Print[b]]


myIf[a > b, Print[a], Print[b]]

The reasoning behind HoldAll (or HoldRest) attribute is well explained in the answer of @Szabolcs.

-

Ignoring why If does what it does, which is well covered in the other answers, I would suggest doing something a little different. Along the lines of what Szabolcs pointed out, if f[x] is expensive to evaluate, the expression

If[Log[f[x]] < 0, f[x], 0]


is rather wasteful. Internally, Mathematica may have saved the value of f[x], but there is no guarantee that this occurred. For instance, if

f[x_] := x RandomReal[];


then the value of f[x] is guaranteed to change between executions. However, since it appears that you plan on reusing the value of f[x], then I would do something like this

With[{val = f[x]}, If[ Log[ val ] < 0, val, 0 ]


which makes the reuse explicit.

-
Or: Replace[f[x], a_ /; Log[a] >= 0 -> 0] –  Mr.Wizard Mar 23 '12 at 23:44
@Mr.Wizard that works, too. But, I tend to think about it in terms of With not Replace. Have to test which is faster ... –  rcollyer Mar 23 '12 at 23:45
I was not suggesting an improvement, but rather giving people something else to think about. In general I think With is more clear and should be preferred. (Of course, I'd use & because I like things terse...) –  Mr.Wizard Mar 23 '12 at 23:46
By the way, fast reply! –  Mr.Wizard Mar 23 '12 at 23:48
@Mr.Wizard I'm in the chat room with everyone else, apparently. –  rcollyer Mar 23 '12 at 23:49