Вопрос о состоянии (/;)

Condition имеет атрибут HoldAll, который предотвращает оценку его первого аргумента перед применением Condition. Но по какой-то причине Condition оценивает свой первый аргумент, даже если тест дает False:

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

Почему это? Для каких целей Condition оценивает свой первый аргумент, если тест дает False? В каких случаях это поведение может быть полезно?

P.S. Его поведение отличается, когда Condition используется в качестве второго аргумента SetDelayed:

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

Это то, что я ожидал от всех случаев.

Ответ 1

Насколько я могу судить (и об этом уже говорили другие ответчики), Condition не следует рассматривать как автономную функцию, а как оболочку, используемую при формировании более крупных выражений с использованием шаблонов. Но я хочу подчеркнуть, что часть тонкости здесь проистекает из того, что Rule и RuleDelayed являются объектами конкретизации. В общем случае конструкторы с областью видимости должны иметь этап связывания переменных, где они разрешают возможные конфликты в именах переменных и фактически связывают переменные с их вхождениями в тело схемы определения области видимости (или в правлении правила для Rule и RuleDelayed). Это можно рассматривать как часть внутренней работы конкретизирующих конструкций, но поскольку Mathematica допускает манипуляции на верхнем уровне с помощью атрибутов и таких вещей, как Evaluate, конспекты не являются черным ящиком, как они могут казаться - мы можем изменить привязки, заставляя объявления переменных, или тело, или и то и другое, оценивать до того, как произойдет связывание - например, удалив некоторые из атрибутов Hold*. Я более подробно обсуждал эти вещи здесь, хотя, не зная точную информацию о реализации для конспектов, мне приходилось в основном угадывать.

Возвращаясь к случаю Rule, RuleDelayed и Condition, поучителю Trace один из рассмотренных примеров:

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}}

Что вы видите, есть специальные внутренние головки RuleCondition и $ConditionHold, которые появляются, когда Condition используется с Rule или RuleDelayed. Я предполагаю, что они реализуют механизм для включения условий в переменные шаблона, включая привязку переменных. Когда вы используете Condition как отдельную функцию, они не отображаются. Эти головы имеют решающее значение для того, чтобы механизм функционирования действительно работал. Вы можете посмотреть, как они работают в Rule и 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

Вы можете видеть, что, скажем, Cases выбирает только элементы формы $ConditionHold[$ConditionHold[something]] и игнорирует те, где RuleCondition приводит к Fail. Теперь, что происходит, когда вы используете Condition, поскольку отдельная функция различна - таким образом, разница в результатах.

Один хороший пример, о котором я знаю, который хорошо иллюстрирует вышеприведенные моменты, находится в этот поток, где возможно реализация версии of With, который связывается последовательно, обсуждаются. Я повторю часть этой дискуссии здесь, потому что это поучительно. Идея заключалась в том, чтобы создать версию With, где предыдущие объявления могут использоваться для объявлений в дальнейшем вниз по списку деклараций. Если мы назовем его Let, то, например, для кода типа

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]

мы должны получить

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

Одна из предложенных реализаций и дает этот результат:

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]]

(это связано с Bastian Erdnuess). Здесь происходит то, что этот Let выполняет привязки во время выполнения, а не во время определения функции. И как только мы хотим использовать общие локальные переменные, он терпит неудачу:

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

Если бы он работал правильно, и мы должны были бы получить 2 разных определения. И здесь мы приходим к сути вопроса: поскольку этот Let действует во время выполнения, SetDelayed не воспринимает Condition как часть шаблона - он будет делать это для With, Block, Module, но не неизвестно Let. Итак, оба определения ищут Mathematica одинаково (с точки зрения шаблонов), и, следовательно, второй заменяет первый. Но это еще не все. Теперь мы создаем только первое определение и пытаемся выполнить:

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

Если вы проследите за последним исполнением, было бы непонятно, почему Condition здесь не запускался. Причина в том, что мы испортили этап связывания. Вот моя улучшенная версия, свободная от этих недостатков:

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]]]];

Что происходит, так это то, что он расширяет LetL во вложенные With во время определения, а не во время выполнения, и это происходит до стадии привязки. Теперь давайте посмотрим:

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}

Это отлично работает, и вы можете видеть, что головки RuleCondition и $ConditionHold отображаются правильно. Поучительно посмотреть на полученное определение для ff:

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

Вы можете видеть, что LetL расширилось на время определения, как описано. И после того, как привязка переменной шаблона произошла после этого, все работает нормально. Кроме того, если мы добавим другое определение:

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

Мы видим, что модели теперь воспринимаются как разные по Mathematica.

Последний вопрос заключался в том, почему Unevaluated не восстанавливает поведение RuleDelayed, поврежденного удалением его атрибута HoldRest. Я могу только догадываться, что это связано с необычным поведением RuleDelayed (оно поглощает любое количество оберток Unevaluated вокруг rhs), отмечено в комментариях к этому вопросу.

Подводя итог: одно из наиболее часто используемых целей Condition тесно связано с охватывающими областями конспектирования (Rule и RuleDelayed), и следует учитывать этап привязки переменных в конструкциях оглавления, когда анализируя их поведение.

Ответ 2

Condition использование часто зависит от того, что находится в левой части, поэтому оно должно оценивать LHS как минимум в некоторой степени. Рассмотрим:

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

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

И для этого, и из этого, я бы предположил, что Condition имеет атрибут HoldRest, а не HoldAll. Вероятно, для некоторого внутреннего использования, возможно, связано с SetDelayed использованием HoldAll.