7
$\begingroup$

Probably this question has no application but it helps understand basic issues about Attributes and functions.

I was writing a piece of code where I accidently defined a function like this:

ClearAll[f1]
SetAttributes[f1, HoldAll]
f1[list_List] := "evaluated"

When evaluated I get results as expected:

f1[{1, 2}]
(*"evaluated"*)
l={1,2};
 f1[l]
(*f1[l]*)

But when I tried to check if this works with Pattern test I was surprised that evaluation happened in the function argument:

ClearAll[f2]
SetAttributes[f2, HoldAll]
f2[list_?(Head[#] == List &)] := "evaluated"
f2[{1, 2}]
(*"evaluated"*)
f2[l]
(*"evaluated"*)

Same result with Pattern Condition /;

My question is that what kind of behavior is this and why does not HoldAll hold the argument (which I understand is anything inside the brackets [] of the function) from evaluation and is this behavior same for all other Attributes?

Thank you.

$\endgroup$
  • 2
    $\begingroup$ FYI this is discussed in the Possible Issues section of the PatternTest documentation $\endgroup$ – Simon Woods Dec 29 '15 at 22:38
  • $\begingroup$ @SimonWoods Good to know, thanks. I wasn't aware that this has been discussed in the docs. $\endgroup$ – Leonid Shifrin Dec 29 '15 at 22:52
10
$\begingroup$

General explanation

The semantics of PatternTest and Condition is that the main evaluator is called to test the pattern, when those are used. It is called internally by the pattern-matcher, and this call is independent from the main evaluation procedure for the main expression. The reason is that the evaluations used to determine the pattern match should be considered sub-evaluations, rather than a part of the standard evaluation procedure for the main expression being evaluated.

This means that Hold* - attributes don't prevent evaluation requested by the pattern-matcher to test the fact of the match. This is one reason why one has to be careful when using such tests, to avoid evaluation leaks introduced through Condition and PatternTest via the pattern-matcher. In particular, you may want to use the construct like

f2[list_?(Function[Null, Head[Unevaluated @ #] == List, HoldAll])] := "evaluated"

to make it roughly equivalent to the first pattern you considered.

Relevant Links

  • Apparently, as noted by Simon Woods in comments, this issue is discussed in the Possible issues section of the PatternTest documentation page
  • A related discussion in a very similar context can be found here
  • I have considered a similar situation also in this answer.
$\endgroup$
  • $\begingroup$ Clear but one more thing to make it fully clear. if the function is defined like this: f2[list_List?(Head[#] == List &)] := "evaluated" then no evaluation happened and the PatternTest is not able to call the evaluator . $\endgroup$ – Algohi Dec 29 '15 at 22:32
  • $\begingroup$ @Algohi Yes, because _List acts as a prefiltering device. This means that the pattern-matcher stops at that point and does not need to call the test function, because it rejects the pattern at an earlier stage of _List. $\endgroup$ – Leonid Shifrin Dec 29 '15 at 22:36
  • $\begingroup$ Aha, it is like that the checking of the argument starts and proceed sequentially. If one pattern/condition passed than proceed to the next. $\endgroup$ – Algohi Dec 29 '15 at 22:38
  • $\begingroup$ @Algohi I'd rather say that for patterns of the type p_head /; test[p] or p_head?test, the procedure to match the pattern is that first, the p_head part is tested, and if the pattern doesn't match, the test from Condition or PatternTest is simply not performed. So yes, this is a particular form of short-circuiting, very sensible one IMO. I have discussed it in this answer. $\endgroup$ – Leonid Shifrin Dec 29 '15 at 22:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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