12

Condition has attribute HoldAll which prevents evaluation of its first argument before applying the Condition. But for some reason Condition evaluates its first argument even if the test gives False:

In[1]:= Condition[Print[x],False]
During evaluation of In[1]:= x
Out[1]= Null/;False

Why is this? For what purposes Condition evaluates its first argument if the test gives False? In which cases this behavior can be useful?

P.S. Its behavior differs when the Condition is used as the second argument of SetDelayed:

In[5]:= f:=Condition[Print[x],False]; f
Out[6]= f

This is what I expected for the all cases.

Alexey Popkov
  • 9,355
  • 4
  • 42
  • 93
  • @Mr.wizard good morning/afternoon/evening/night! @Alexey I don't know about HoldAll but isn't Condition supposed to work on patterns, RuleDelayed and SetDelayed only, not anything else? At least, that's what in the docs. Your usage is undocumented. – Sjoerd C. de Vries May 03 '11 at 07:28
  • @Sjoerd I understand but in *Mathematica* all the functions are supposed to be working in a very general way. – Alexey Popkov May 03 '11 at 07:56
  • 3
    As you possibly know, Ted Ersek discusses [here](http://www.verbeia.com/mathematica/tips/HTMLLinks/Tricks_A-K_19.html) an unrelated subtlety of `Condition` (and a use of `Update`). The [reply](http://forums.wolfram.com/mathgroup/archive/2005/Oct/msg00208.html) of Carl Woll to an old Mathgroup question from David Park might also be of general interest. – 681234 May 04 '11 at 16:30

2 Answers2

9

As far as I can tell (and this has been mentioned by other answerers already), Condition should not be thought of as a standalone function, but as a wrapper used in forming larger expressions involving patterns. But I want to stress that part of the subtlety here comes from the fact that Rule and RuleDelayed are scoping constructs. In general, scoping constructs must have a variable-binding stage, where they resolve possible conflicts in variable names and actually bind variables to their occurrences in the body of the scoping construct (or, in the r.h.s. of the rule for Rule and RuleDelayed). This may be considered a part of the inner workings of the scoping constructs, but, because Mathematica allows top-level manipulations through attributes and things like Evaluate, scoping constructs are not as black-box as they may seem - we may change the bindings by forcing the variable declarations, or the body, or both, to evaluate before the binding happens - for example, by removing some of the Hold* - attributes. I discussed these things here in somewhat more detail, although, not knowing the exact implementation details for the scoping constructs, I had to mostly guess.

Returning back to the case of Rule, RuleDelayed and Condition, it is instructive to Trace one of the examples discussed:

In[28]:= Trace[Cases[{3,3.},a_:>Print[a]/;(Print["!"];IntegerQ[a])],RuleCondition,TraceAbove->All]
During evaluation of In[28]:= !
During evaluation of In[28]:= !
During evaluation of In[28]:= 3

Out[28]= {Cases[{3,3.},a_:>Print[a]/;(Print[!];IntegerQ[a])], 
{RuleCondition[$ConditionHold[$ConditionHold[Print[3]]],True],
      $ConditionHold[$ConditionHold[Print[3]]]},
{RuleCondition[$ConditionHold[$ConditionHold[Print[3.]]],False],Fail},
 {Print[3]},{Null}}

What you see is that there are special internal heads RuleCondition and $ConditionHold, which appear when Condition is used with Rule or RuleDelayed. My guess is that these implement the mechanism to incorporate conditions on pattern variables, including the variable binding. When you use Condition as a standalone function, these don't appear. These heads are crucial for condition mechanism to really work. You can look at how they work in Rule and RuleDelayed:

In[31]:= RuleCondition[$ConditionHold[$ConditionHold[Print[3.`]]],True]
Out[31]= $ConditionHold[$ConditionHold[Print[3.]]] 

In[32]:= RuleCondition[$ConditionHold[$ConditionHold[Print[3.`]]],False]
Out[32]= Fail

You can see that, say, Cases picks up only elements of the form $ConditionHold[$ConditionHold[something]], and ignore those where RuleCondition results in Fail. Now, what happens when you use Condition as a stand-alone function is different - thus the difference in results.

One good example I am aware of, which illustrates the above points very well, is in this thread, where possible implementations of a version of With which binds sequentially, are discussed. I will repeat a part of that discussion here, since it is instructive. The idea was to make a version of With, where previous declarations can be used for declarations further down the declaration list. If we call it Let, then, for example, for code like

Clear[h, xl, yl];
xl = 1;
yl = 2;
h[x_, y_] := Let[{xl = x, yl = y + xl + 1}, xl^2 + yl^2];
h[a, b]

we should get

a^2+(1+a+b)^2

One of the implementations which was suggested, and gives this result, is:

ClearAll[Let];
SetAttributes[Let, HoldAll];
Let /: (lhs_ := Let[vars_, expr_ /; cond_]) := 
   Let[vars, lhs := expr /; cond]
Let[{}, expr_] := expr;
Let[{head_}, expr_] := With[{head}, expr]
Let[{head_, tail__}, expr_] := With[{head}, Let[{tail}, expr]]

(this is due to Bastian Erdnuess). What happens here is that this Let performs bindings at run-time, rather than at the time when function is being defined. And as soon as we want to use shared local variables, it fails:

Clear[f];
f[x_,y_]:=Let[{xl=x,yl=y+xl+1},xl^2+yl^2/;(xl+yl<15)];
f[x_,y_]:=x+y;

?f
Global`f
f[x_,y_]:=x+y

Had it worked correctly, and we should have ended up with 2 distinct definitions. And here we come to the crux of the matter: since this Let acts at run-time, SetDelayed does not perceive the Condition as a part of the pattern - it would do that for With, Block, Module, but not some unknown Let. So, both definitions look for Mathematica the same (in terms of patterns), and therefore, the second replaces the first. But this is not all. Now we only create the first definition, and try to execute:

Clear[f];
f[x_, y_] := Let[{xl = x, yl = y + xl + 1}, xl^2 + yl^2 /; (xl + yl < 15)];

In[121]:= f[3, 4]

Out[121]= 73 /; 3 + 8 < 15

If you trace the last execution, it would be very unclear why the Condition did not fire here. The reason is that we messed up the binding stage. Here is my improved version, which is free from these flaws:

ClearAll[LetL];
SetAttributes[LetL, HoldAll];
LetL /: Verbatim[SetDelayed][lhs_, rhs : HoldPattern[LetL[{__}, _]]] :=
   Block[{With}, Attributes[With] = {HoldAll};
     lhs := Evaluate[rhs]];
LetL[{}, expr_] := expr;
LetL[{head_}, expr_] := With[{head}, expr];
LetL[{head_, tail__}, expr_] := 
  Block[{With}, Attributes[With] = {HoldAll};
    With[{head}, Evaluate[LetL[{tail}, expr]]]];

What is does is that it expands LetL into nested With at definition-time, not run-time, and that happens before the binding stage. Now, let us see:

In[122]:= 
Clear[ff];
ff[x_,y_]:=LetL[{xl=x,yl=y+xl+1},xl^2+yl^2/;(xl+yl<15)];

Trace[ff[3,4]]

Out[124]= {ff[3,4],       
{With[{xl$=3},With[{yl$=4+xl$+1},RuleCondition[$ConditionHold[$ConditionHold[xl$^2+yl$^2]],
 xl$+yl$<15]]],With[{yl$=4+3+1},RuleCondition[$ConditionHold[$ConditionHold[3^2+yl$^2]],3+yl$<15]],
{4+3+1,8},RuleCondition[$ConditionHold[$ConditionHold[3^2+8^2]],3+8<15],
{{3+8,11},11<15,True},RuleCondition[$ConditionHold[$ConditionHold[3^2+8^2]],True],
$ConditionHold[$ConditionHold[3^2+8^2]]},3^2+8^2,{3^2,9},{8^2,64},9+64,73}

This works fine, and you can see the heads RuleCondition and $ConditionHold showing up all right. It is instructive to look at the resulting definition for ff:

?ff
Global`ff
ff[x_,y_]:=With[{xl=x},With[{yl=y+xl+1},xl^2+yl^2/;xl+yl<15]]

You can see that LetL has expanded at definition-time, as advertised. And since pattern variable binding happened after that, things work fine. Also, if we add another definition:

ff[x_,y_]:=x+y;

?ff
Global`ff
ff[x_,y_]:=With[{xl=x},With[{yl=y+xl+1},xl^2+yl^2/;xl+yl<15]]

ff[x_,y_]:=x+y

We see that the patterns are now perceived as different by Mathematica.

The final question was why Unevaluated does not restore the behavior of RuleDelayed broken by the removal of its HoldRest attribute. I can only guess that this is related to the unusual behavior of RuleDelayed (it eats up any number of Unevaluated wrappers around the r.h.s.), noted in the comments to this question.

To summarize: one of the most frequent intended uses of Condition is closely tied to the enclosing scoping constructs (Rule and RuleDelayed), and one should take into account the variable binding stage in scoping constructs, when analyzing their behavior.

Community
  • 1
  • 1
Leonid Shifrin
  • 22,449
  • 4
  • 68
  • 100
  • Gosh Leonid, you sure write a lot! Maybe you should consider writing a book or something... – Mr.Wizard May 03 '11 at 14:15
  • 1
    @Mr.Wizard I don't know of a good *and* short example to illustrate my point, and besides, these are rather subtle issues that I don't completely understand myself. Regarding the book, thanks for the hint, already considered :) – Leonid Shifrin May 03 '11 at 14:19
  • Knowing the details of the mission of the `RuleCondition` and `$ConditionHold` all these things would be MUCH more clear and easy expressible. Thanks for this great analysis, I need some time to understand it. One note on the mission of the `Condidtion`: if the `Condition` is not a standalone function it looks like a defect that it ever works when is called as a standalone function. It should stay unevaluated according to its attribute `HoldAll`! – Alexey Popkov May 03 '11 at 14:22
  • @Mr.Wizard Sure I did, but the truth is that I also would prefer a shorter explanation, just like shorter code. @Alexey's comment is right on target - had we known more about this stuff, we would probably be able to find shorter ways to explain it. – Leonid Shifrin May 03 '11 at 14:27
  • 2
    @Alexey As @Mr.Wizard has demonstrated, there can be productive uses of this behavior. And as we all know, `HoldAll` does not imply that the function does not evaluate the argument, but merely that the function is in a position to decide whether or not to evaluate it. – Leonid Shifrin May 03 '11 at 14:29
  • @Leonid How the `Condition` can be classified in accordance with [this](http://reference.wolfram.com/mathematica/tutorial/TheMeaningOfExpressions.html) documentation page? Does it match the listed interpretations at all? Are there other symbols with such behavior of a semi-wrapper -- semi-function? – Alexey Popkov May 03 '11 at 14:42
  • 1
    @Alexey I'd say it is a *Head*. I would not consider it a function. Of course, all this classification is subjective. Mathematica does not need it, we do. And I think this is what makes mma so powerful - the fact that expressions are so general that they can be used for such different purposes or their combinations. – Leonid Shifrin May 03 '11 at 14:47
  • 1
    @Alexey `$ConditionHold` _seems_ straightforward: to Hold `Condition`. And it does (at least) that: `$ConditionHold[Print["!"] /; False]` I suspect that it is used rather than Hold simply so that it can selectively be addressed or released. – Mr.Wizard May 03 '11 at 14:57
  • 1
    @Mr.Wizard I'd guess that `$ConditionHold` is actually just a `HoldAll` - carrying wrapper with a special name (known to certain functions like `Cases` etc), and not much else. And it seems to hold not the condition, but the code to execute if condition is satisfied. I may be wrong of course. – Leonid Shifrin May 03 '11 at 14:57
  • Leonid, that is what I was working my way toward, but again you said it better. That's what happens when I keep editing these comments. Ping . pong . ping . pong ... ;-) – Mr.Wizard May 03 '11 at 15:00
  • @Mr. Wizard - funny, I also thought of ping-pong. I just thought we reached the limitation of SO way of doing things, should switch to a chat, and perhaps a collaboratively-edited web-based mma front-end would be a good addition ? :) – Leonid Shifrin May 03 '11 at 15:04
  • @Mr.Wizard I knew about the site, but I did not know that one can execute mma code right on the page. Still, this falls a bit short of the level of interactivity I meant. – Leonid Shifrin May 03 '11 at 15:12
  • Sorry, I should have acknowledged that it was not nearly what you meant, but what you said made me think of that site, and I wondered if it might be useful in some (other) way. – Mr.Wizard May 03 '11 at 15:16
  • @Mr.Wizard My understanding is that that site serves a different purpose, more like a collaboratively-edited hub for mma-related links, files and other resources, and it seems quite useful. – Leonid Shifrin May 03 '11 at 15:22
5

Condition use often depends on what is in the left hand side, so it must evaluate the LHS at least to some degree. Consider:

MatchQ[3, a_ /; IntegerQ[a]]
   True
p = {a_, b_};

MatchQ[{3, 0.2}, p /; IntegerQ[a] && b < 1]
   True

Both for this and from this, I would have guessed that Condition had attribute HoldRest rather than HoldAll. It probably needs HoldAll for some internal use, perhaps related to the SetDelayed usage.

Mr.Wizard
  • 24,179
  • 5
  • 44
  • 125
  • With attribute `HoldRest` the [Villegas-Gayley's trick](http://stackoverflow.com/questions/4198961/what-is-in-your-mathematica-tool-bag/5149656#5149656) would not be possible as well as right behavior in such cases as `Cases[{3, 3.}, a_ :> Print[a] /; IntegerQ[a]]`. – Alexey Popkov May 03 '11 at 07:14
  • @Alexey, isn't the `Cases` example because of `RuleDelayed` rather than a Hold attribute of `Condition`? I think the "trick" could fall into the special handling of `/;` within `:=` that you illustrated. – Mr.Wizard May 03 '11 at 07:20
  • @Mr.Wizard You are right. `Unprotect[Condition]; ClearAttributes[Condition, HoldAll]; Cases[{3, 3.}, a_ :> Print[a] /; IntegerQ[a]]` does not change the behavior mentioned. It seems that when the `Condition` is placed inside of the second argument of `RuleDelayed` and `SetDelayed` and probably `TagSetDelayed` and `UpSetDelayed` works in completely different way than by default. It is interesting that in the case of `RuleDelayed` it works differently even is placed inside of the first argument: `Cases[{3, 3.}, (Print["!"]; a_) /; IntegerQ[a] :> (Print[a] /; IntegerQ[a]; Print["!!"])]`. – Alexey Popkov May 03 '11 at 07:35
  • @Alexey, placement is indeed unusual about `/;` and different people have different conventions about where to put it. If you clear attributes for `/;` and then try: `Cases[{3, 3.}, a_ /; IntegerQ[a] :> Print[a]]` you see it fails, because `IntegerQ[a]` evaluates to `False`. – Mr.Wizard May 03 '11 at 07:44
  • I have made a mistake in the above comment: inside of the first argument of `RuleDelayed` the behavior of `Condition` is the same: the first argument is evaluated for the first time and only `a_` stays as the first argument of `Condition` for the future calls to `Condition` from `Cases`. It seems that it is because of internal optimizations of `Cases`. – Alexey Popkov May 03 '11 at 07:48
  • @Mr.Wizard You mean by "different people" different developers? – Alexey Popkov May 03 '11 at 07:51
  • @Alexey, yes, that's all I meant. – Mr.Wizard May 03 '11 at 07:55
  • @Mr.Wizard The `HoldRest` attribute of `RuleDelayed` is important: `Unprotect[RuleDelayed]; ClearAttributes[RuleDelayed, HoldRest]; Cases[{3, 3.}, a_ :> Print[a] /; (Print["!"]; IntegerQ[a])]`. The unusual side is that the default behavior cannot be restored by `Unevaluated`: `Unprotect[RuleDelayed]; ClearAttributes[RuleDelayed, HoldRest]; Cases[{3, 3.}, a_ :> Unevaluated[Print[a] /; (Print["!"]; IntegerQ[a])]]`. – Alexey Popkov May 03 '11 at 08:41