Ответ 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
), и следует учитывать этап привязки переменных в конструкциях оглавления, когда анализируя их поведение.