【问题标题】:Why doesn't RuleDelayed hold Unevaluated?为什么 RuleDelayed 不保持未评估?
【发布时间】:2011-06-07 14:53:29
【问题描述】:

Mathematica 的求值器通常持有(或恢复?)Heads Unevaluated 作为 Symbols 的参数提供的表达式:

In[1]:= f[s, Unevaluated[1 + 1]]

Out[2]= f[s, Unevaluated[1 + 1]]

In[5]:= Trace[f[s,Unevaluated[1+1]],TraceOriginal->True]

Out[5]= {f[s,Unevaluated[1+1]],{f},{s},f[s,1+1],f[s,Unevaluated[1+1]]}

RuleDelayed 并非如此。此外,在RuleDelayed 的情况下,任何Unevaluated 包装器都会被剥离:

In[1]:= Attributes@RuleDelayed
RuleDelayed[s, Unevaluated[1 + 1]]
RuleDelayed[s, Unevaluated@Unevaluated[1 + 1]]
RuleDelayed[s, Unevaluated@Unevaluated@Unevaluated[1 + 1]]
RuleDelayed[Unevaluated@Unevaluated@Unevaluated[1 + 1], 1 + 1]

Out[1]= {HoldRest, Protected, SequenceHold}

Out[2]= s :> 1 + 1

Out[3]= s :> 1 + 1

Out[4]= s :> 1 + 1

Out[5]= 2 :> 1 + 1

为什么在RuleDelayed 的情况下,评估器会剥离任何Unevaluated 包装器?它对哪些用途有用?是否可以为任意Symbol(例如f)模拟这种行为?


也不清楚为什么Trace 显示的RuleDelayedf 更复杂:

In[2]:= Trace[RuleDelayed[s,1+1],TraceOriginal->True]
Out[2]= {s:>1+1,{RuleDelayed},{s},s:>1+1,s:>1+1,{RuleDelayed},{s},s:>1+1}

看起来RuleDelayed 被评估了两次...

【问题讨论】:

  • 如果不删除它...不会导致两个嵌套的Unevaluated
  • @belisarius 对于f[s, Unevaluated[1 + 1]],一切都很简单。从哪里我们会得到两个嵌套的Unevaluated
  • @Sasha 在我之前回答(而且更好)
  • “但 RuleDelayed 并非如此,尽管它甚至具有 HoldRest 属性” - 正如我们多次讨论过的那样,HoldRest (几乎)无关紧要 - 它只是将头部置于决定如何处理传递给它的原始表达式。如果 Unevaluted 的标准语义总是被强制执行,那么 evaluator 本身也是无关紧要的。真正的问题 IMO(我当然可能是错的)是为什么 RuleDelayed 的行为方式如此,或者,如果仍然证明评估者是罪魁祸首,为什么对评估者的行为做出例外处理.
  • 我认为函数属性确实与评估者有关。我的意思是属性与问题无关,至少如果您将其重新表述为“为什么 any 数量的Unevaluated 包装器被RuleDelayed 剥离”。正如我在下面提到的cmets,Unevaluated 的标准语义(也与评估器有关)不能解释在单个评估中剥离多个 Unevaluated 包装器,也不能解释在剥离单层 Unevaluated 时重新评估- 这些必须是相关头部的属性(RuleDelayed)

标签: wolfram-mathematica


【解决方案1】:

此答案应被视为对@Sasha 答案的补充。我认为这是一个微妙的话题,可以从多个角度的解释中受益。

为什么这个问题很重要

我想强调的是,所讨论的行为并不典型,因为它不是大多数人在 Mathematica 中的行为方式,并且不能仅根据评估的一般原则来解释(尤其是 @ 987654321@ 剥离),而无需借助具有此类行为的特定头部的实现细节(RuleDelayed 此处)。考虑一些具有HoldRest 属性的通用头部:

In[185]:= SetAttributes[h, HoldRest];
h[1, Unevaluated[Unevaluated[Unevaluated[1 + 1]]]]

Out[186]= h[1, Unevaluated[Unevaluated[Unevaluated[1 + 1]]]]

同时

In[209]:= 1:>Unevaluated@Unevaluated@Unevaluated[1+1]

Out[209]= 1:>1+1

关于剥离 Unevaluated 包装的一些细节

这是基于 David Wagner 的书“Power programming in Mathematica - the kernel”中的讨论、David Withoff 名为“Mathematica internals”的 WRI 技术报告以及我自己的经验。

这是一个非常简化的评估图。 Mathematica 递归地计算表达式,首先从“分支”(表达式)“向下”到“子分支”(子表达式)和叶子(原子),然后“向上”。在“向下”的过程中,(子)表达式的头部被评估,然后是部分。具有头部Unevaluated 的那些部分不会被进一步评估(在某种意义上,评估器不会在它们上递归调用),而Unevaluated 被剥离并被标记为已完成。在“向上”的过程中,认为零件已经被评估过。有许多步骤,包括序列拼接,与FlatOrderless等属性相关的评估。然后,应用当前评估的头部规则,用户定义和内置(UpValuesDownValuesSubValues)。最后,这对于本次讨论很重要,Unevaluated 包装器将针对未找到适用规则的表达式部分进行恢复。这就是为什么对于一个未定义的函数f,我们有:

In[188]:= ClearAll[f];
f[Unevaluated[1+1]]

Out[189]= f[Unevaluated[1+1]]

可以通过使用Trace 并将TraceOriginal 选项设置为True 来确认Unevaluated 包装器已被剥离然后恢复:

In[190]:= Trace[f[Unevaluated[1+1]],TraceOriginal->True]

Out[190]= {f[Unevaluated[1+1]],{f},f[1+1],f[Unevaluated[1+1]]} 

当为f 定义了一些规则时会发生什么?答案是每个规则应用程序都会剥离一层Unevaluated。这是一个例子:

In[204]:= 
f[x_]:=Hold[x]; 
g[x_]:=f[x];
{f[Unevaluated[1+1]],g[Unevaluated[1+1]]}
{f[Unevaluated@Unevaluated[1+1]],g[Unevaluated@Unevaluated[1+1]]}
{f[Unevaluated@Unevaluated@Unevaluated[1+1]], g[Unevaluated@Unevaluated@Unevaluated[1+1]]}

Out[206]= {Hold[1+1],Hold[2]}
Out[207]= {Hold[Unevaluated[1+1]],Hold[1+1]}
Out[208]= {Hold[Unevaluated[Unevaluated[1+1]]],Hold[Unevaluated[1+1]]}

如果人们确切知道表达式的给定部分将参与多少次评估,原则上可以将该部分包装在Unevaluated 的那么多层中以防止其评估。然而,这些信息通常是不可能拥有的,并且Unevaluated 不应该用作持久保存包装器——这就是Hold 的用途。但是这种分析可能更清楚地表明,为了触发 任何 次评估,执行它的头部必须为它定义非平凡的规则。换句话说,通常情况下,由剥离Unevaluated 层组成的评估过程部分(其本身,“在表达式向下的过程中”)不会引起它的重新评估 - 这只能在途中发生“向上”,由于为该头部定义了一些规则。结论是观察到RuleDelayed 的行为只能通过查看RuleDelayed 的实现细节来解释,一般考虑是不够的。

插图:模拟RuleDelayed的行为

我现在将对此进行说明,并回答原始问题中有关模拟此行为的部分。据我所知,以下代码完全模拟了 RuleDelayed 关于剥离 Unevaluated 包装器的行为:

ClearAll[rd];
SetAttributes[rd, {HoldAllComplete, SequenceHold}];
rd[lhs_, Verbatim[Unevaluated][rhs_]] /;
   Head[Unevaluated[rhs]] =!= Unevaluated := Append[rd[lhs], Unevaluated[rhs]];
rd[lhs_, Verbatim[Unevaluated][rhs_]] := rd @@ {lhs, rhs};
rd[lhs_, rhs_] /; Hold[lhs] =!= Hold[Evaluate[lhs]] := Prepend[rd[rhs], lhs];

(对于其他负责人来说,它可能不会没有一些评估泄漏,但这不是重点。另外,我无法做到HoldRest,比如RuleDelayed - 我不得不为此使用HoldAllComplete施工工作)。您可以检查:

In[173]:= 
a=1;
rd[a,Unevaluated[1+1]]
rd[a,Unevaluated@Unevaluated[1+1]]
rd[a,Unevaluated@Unevaluated[1+1]]

Out[174]= rd[1,1+1]
Out[175]= rd[1,1+1]
Out[176]= rd[1,1+1]

这间接支持了我的论点,即它可能是 RuleDelayed 实现,而不是评估器,负责这种效果(尽管不确定,我只能猜测。另外,RuleDeleayed 是足够基本的,这异常行为可能已连接到评估器)

编辑

为了进一步加强类比,以下是追踪的结果:

In[183]:= 
DeleteCases[Trace[rd[s,Unevaluated[1+1]],TraceOriginal->True],
    x_/;!FreeQ[x,Head|Hold|Append|HoldPattern[rd[_]]]]

Out[183]= {rd[s,Unevaluated[1+1]],{rd},rd[s,Unevaluated[1+1]],
    rd[s,1+1],{rd},rd[s,1+1],rd[s,1+1]}

In[184]:= Trace[RuleDelayed[s,Unevaluated[1+1]],TraceOriginal->True]

Out[184]= {s:>Unevaluated[1+1],{RuleDelayed},{s},s:>1+1,s:>1+1,{RuleDelayed},{s},s:>1+1}    

追踪结果非常相似。我使用DeleteCases 过滤掉rd 的中间评估。差异是由于rdHoldAllComplete 属性与RuleDelayedHoldRest 属性。

【讨论】:

  • 您的函数定义与 lhs 的评估不一致:第一个定义保留它,而第二个定义进行评估。
  • @Alexey 好收获!我更新了代码和测试示例,以解决这个问题。
  • @Leonid 我同意,这也是我说的,RuleDelayed 的实现细节导致重复评估,最终导致Unevaluated 被剥离。在我的答案下方的 cmets 中,我提供了另一个命令的示例,该命令出于完全相同的原因导致双重评估。发生这种情况是因为表达式经过验证,并且一旦验证,它就会被打上某个有效标志。设置有效标志启动重新评估序列。这种情况一直持续到表达式不再改变为止。
  • @Alexey 完全模仿不是我的目标,但您可以将最后一个定义更改为rd[lhs_, rhs_] := With[{evalLhs = lhs}, Prepend[rd[rhs],lhs] /; HoldComplete[lhs] =!= HoldComplete[evalLhs]]; 以避免这个缺陷。这也将避免当前版本的另一个缺陷 - 对于一般 l.h.s.,它被评估两次,这是不好的 - l.h.s.可能含有副作用。新版本评估 l.h.s.只有一次。
  • @Alexey 您可以使用Prepend[Unevaluated[rd[rhs]], lhs],但由于rd 没有1 个参数的规则,所以我看不出有什么意义。我也不认为用所有的花里胡哨来伪造RuleDelayed精确模仿是值得的——我们已经看到了主要原理,意识到效果是由@987654375的细节引起的@,我认为这是重点。
【解决方案2】:

Unevaluated 在规则中作为最外层包装出现时会被剥离。这就是Unevaluated 的工作方式,即Unevaluated 不是评估任何东西的常规符号。这是一个令牌。比较

In[8]:= f[s_] := g[Unevaluated[1 + 1]]

In[9]:= DownValues[f]

Out[9]= {HoldPattern[f[s_]] :> g[Unevaluated[1 + 1]]}

In[10]:= f[s_] := Unevaluated[1 + 1]

In[11]:= DownValues[f]

Out[11]= {HoldPattern[f[s_]] :> 1 + 1}

因为求值器是递归的,最终去掉Unevaluated就足够了。


编辑根据 Alexey 的评论扩展答案:

Unevaluated 是一个惰性符号,一个被评估者识别并在规则范围内采取行动的令牌。因此,Unevaluated[1+1]f[1,Unevaluated[1+1]] 保持不变。在评估 RuleDelayed[s,Unevaluated[1+1]] 时,会剥离 Unevaluated,然后根据评估原则重新评估整个 RuleDelayed 表达式。


EDIT 2 这是对重新评估原因的讨论的浓缩结果
`RuleDelayed` 的实现细节导致重复评估,最终导致 Unevaluated 被剥离。在我的答案下方的 cmets 中,我提供了另一个命令的示例,该命令出于完全相同的原因导致双重评估。发生这种情况是因为表达式经过验证,并且一旦验证,它就会被打上某个有效标志。设置有效标志启动重新评估序列。这种情况一直发生,直到表达式不再改变。

其他需要验证的表达式也会出现类似的效果,例如Root object:

In[41]:= Root[#1^6 + #1 - 1 & , 1]; Trace[Root[#1^6 + #1 - 1 & , 1], 
 TraceOriginal -> True]

Out[41]= {
 HoldForm[Root[#1^6 + #1 - 1 & , 1]], {HoldForm[Root]}, 
   {HoldForm[#1^6 + #1 - 1 & ], {HoldForm[Function]}, 
  HoldForm[#1^6 + #1 - 1 & ]}, 
   {HoldForm[1]}, 
  HoldForm[Root[#1^6 + #1 - 1 & , 1]],    <-- here the root had been 
                                              stamped valid, and reevaluated
   HoldForm[Root[-1 + #1 + #1^6 & , 1]]   <-- evaluation was trivial.

}

【讨论】:

  • 但它的作用是什么?在什么情况下有用?另请参阅问题底部的补充:为什么Trace 表明RuleDelayed 被评估了两次?
  • @Alexey 请查看包含问题答案的编辑。
  • 这个评估原则是否只与规则评估有关?或者还有其他情况会再次重新评估整个表达式?在其他符号的简单情况下,它不会发生:Trace[ff[s,1+1],TraceOriginal-&gt;True] 仅显示对头部 ff 的一次评估,而 Trace[RuleDelayed[s,1+1],TraceOriginal-&gt;True] 显示符号 RuleDelayed 被评估两次。
  • @Alexey Even Trace[RuleDelayed[s, 1], TraceOriginal -&gt; True] 显示双重评估。这可能与为规则中使用的(平凡)模式执行的某些预计算有关。预计算的结果然后与规则“关联”。这种关联会改变表达式,从而导致评估。您也可以在其他表达式中看到这一点。考虑Root[#^6 + # - 1 &amp;, 1]; Trace[Root[#^6 + # - 1 &amp;, 1], TraceOriginal -&gt; True]。第一个Root 是强制加载 Mathematica 包。
  • 目前(v13),Root 不再有这种特殊行为(并且没有加载任何包)。
【解决方案3】:

使用自定义函数“rd”重现 RuleDelayed 的行为

这是根据 Sasha 的描述重现 RuleDelayed 评估行为的简单方法:

<...> [Rule] 表达式经过验证, 并且一旦验证,它就会被标记 某个有效的标志。设置 有效标志启动重新评估 顺序。这种情况一直持续到 表情不再变化。

ClearAll[rd];
SetAttributes[rd, {HoldRest, SequenceHold}];
Options[rd] = {"Validated" -> None};
expr : rd[args__] /; ("Validated" /. Options[Unevaluated[rd]]) =!= 
   Hold[expr] := (Options[
    Unevaluated[rd]] = {"Validated" -> Hold[expr]}; rd[args])

In[6]:= rd[Unevaluated@Unevaluated[1 + 1], 
 Unevaluated@Unevaluated[Unevaluated[1 + 1]]]

Out[6]= rd[2, 1 + 1]

我们可以比较rdRuleDelayed的第一个参数的求值次数:

dummyFunction /; (++numberOfEvaluations; False) := Null;

In[36]:= numberOfEvaluations=0;
rd[dummyFunction,Unevaluated@Unevaluated[Unevaluated[1+1]]];
numberOfEvaluations
numberOfEvaluations=0;
RuleDelayed[dummyFunction,Unevaluated@Unevaluated[Unevaluated[1+1]]];
numberOfEvaluations
Out[38]= 4
Out[41]= 4

跟踪 rd 和 RuleDelayed

以下演示了此版本几乎完全复制了RuleDelayed 的行为。唯一的区别是对最终表达式rd[2,1+1] 的最后一次附加评估,它涉及条件检查并且不提供匹配项。使用rd 作为Trace 的第二个参数时,最后的评估会自动排除。在RuleDelayed 的情况下,最后的检查不能被Trace 捕获,因为它没有经过评估器。

代码:

ClearAll[rd];
SetAttributes[rd, {HoldRest, SequenceHold}];
SetAttributes[returnLast, {HoldRest}]
SetAttributes[{NotValidatedQ, setValidatedFlag}, HoldAllComplete];
Options[rd] = {"Validated" -> None};
NotValidatedQ[expr_] := ("Validated" /. Options[Unevaluated[rd]]) =!= 
   Hold[expr];
setValidatedFlag[expr_] := 
  Options[Unevaluated[rd]] = {"Validated" -> Hold[expr]};
returnLast[first_, last_] := last;
expr : rd[args__] /; NotValidatedQ[expr] := 
 returnLast[setValidatedFlag[expr], rd[args]]

比较:

rdList = DeleteCases[
   Trace[rd[Unevaluated@Unevaluated[1 + 1], 
     Unevaluated@Unevaluated[Unevaluated[1 + 1]]], 
    TraceOriginal -> 
     True], ({HoldForm[(validatedQ | setValidatedFlag)[_]], ___} | 
     HoldForm[_returnLast] | {HoldForm[returnLast]})] /. 
  rd -> RuleDelayed

RuleDelayedList = 
 Trace[RuleDelayed[Unevaluated@Unevaluated[1 + 1], 
   Unevaluated@Unevaluated[Unevaluated[1 + 1]]], 
  TraceOriginal -> True]

使用Trace 的第二个参数进行跟踪显示完全匹配:

In[52]:= rdList = 
  Trace[rd[Unevaluated@Unevaluated[1 + 1], 
     Unevaluated@Unevaluated[Unevaluated[1 + 1]]], rd, 
    TraceOriginal -> True] /. {HoldForm[_returnLast] -> Sequence[], 
    rd -> RuleDelayed};
RuleDelayedList = 
  Trace[RuleDelayed[Unevaluated@Unevaluated[1 + 1], 
    Unevaluated@Unevaluated[Unevaluated[1 + 1]]], RuleDelayed, 
   TraceOriginal -> True];

rdList === RuleDelayedList

Out[54]= True

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-05-19
    • 2019-05-24
    • 2015-03-15
    • 1970-01-01
    • 2011-10-11
    • 2014-06-17
    • 2021-11-01
    • 2012-06-12
    相关资源
    最近更新 更多