这是我已经使用了很长一段时间的版本(最初是为我的书的第二版编写的,但最终我经常使用它)。如果参数表示一些未评估的代码,那么测试函数必须具有 HoldAll 或 HoldFirst 属性,如果我们希望将代表一个特定子句的单个代码以其未评估的形式传递给它(这可能是也可能不是可取的)。
ClearAll[fastOr];
Attributes[fastOr] = {HoldRest};
fastOr[test_, {args___}] := fastOr[test, args];
fastOr[test_, args___] :=
TrueQ[Scan[
Function[arg, If[test[arg], Return[True]], HoldAll],
Hold[args]]];
编辑:我刚刚注意到问题中链接的页面底部的 Daniel Reeves 的解决方案与这个非常相似。主要区别在于我关心短路和保持论点不被评估(见下文),而丹尼尔只关注短路部分。
它确实有短路行为。我们需要HoldRest 属性,因为我们想以未评估的形式保留参数。我们还需要纯函数中的HoldAll(或HoldFirst)属性来保留每个未计算的参数,直到它被传递给test。它在用于test 的主体之前是否被评估现在取决于test 的属性。举个例子:
Clear[fullSquareQ];
fullSquareQ[x_Integer] := IntegerQ[Sqrt[x]];
In[13]:= Or @@ Map[fullSquareQ, Range[50000]] // Timing
Out[13]= {0.594, True}
In[14]:= fastOr[fullSquareQ, Evaluate[Range[10000]]] // Timing
Out[14]= {0., True}
这是一个示例,我们将一些代码片段作为参数传递,从而引发副作用(打印)。最后一个参数的代码没有机会执行,因为在前一个子句中已经确定了结果:
In[15]:= fastOr[# &, Print["*"]; False, Print["**"]; False,
Print["***"]; True, Print["****"]; False]
During evaluation of In[15]:= *
During evaluation of In[15]:= **
During evaluation of In[15]:= ***
Out[15]= True
请注意,由于fastOr 接受一般的未评估代码片段作为Or 的子句,因此如果您不关心它们将在开始(就像上面的 Range 示例一样)。
最后,我将说明 fastOr 的保留代码的编程构造,以展示如何使用它(如果您愿意,可以将其视为关于使用保留表达式的小型速成课程)。以下函数在处理保留表达式时非常有用:
joinHeld[a___Hold] := Hold @@ Replace[Hold[a], Hold[x___] :> Sequence[x], {1}];
例子:
In[26]:= joinHeld[Hold[Print[1]], Hold[Print[2], Print[3]], Hold[], Hold[Print[4]]]
Out[26]= Hold[Print[1], Print[2], Print[3], Print[4]]
以下是我们如何使用它以编程方式构造在上面的 Print-s 示例中使用的保留参数:
In[27]:=
held = joinHeld @@ MapThread[Hold[Print[#]; #2] &,
{NestList[# <> "*" &, "*", 3], {False, False, True, False}}]
Out[27]= Hold[Print["*"]; False, Print["**"]; False, Print["***"]; True, Print["****"]; False]
要将它传递给fastOr,我们将使用另一个有用的习惯用法:追加(或前置)到Hold[args],直到我们获得所有函数参数,然后使用Apply(请注意,一般来说,如果我们不'不希望我们附加/前置的部分进行评估,我们必须将其包装在 Unevaluated 中,所以一般的成语看起来像 Append[Hold[parts___],Unevaluated[newpart]]):
In[28]:= fastOr @@ Prepend[held, # &]
During evaluation of In[28]:= *
During evaluation of In[28]:= **
During evaluation of In[28]:= ***
Out[28]= True
关于你提到的原始实现,你可以看看我前段时间对它的评论。问题是 TakeWhile 和 LengthWhile 在 v. 8.0.0 中存在打包数组的错误,它们在 8.0.1 的源代码中已修复 - 因此,从 8.0.1 开始,您可以使用我的版本或 Michael 的版本。
HTH
编辑:
我刚刚注意到,在您提到的帖子中,您想要一种不同的语法。虽然在这种情况下采用fastOr 中采用的方法并不是很困难,但这里有一个不同的实现,可以说它与这种特定语法的现有语言结构更接近。我建议使用Table 和异常,因为Table 中的迭代器接受与您想要的相同的语法。这里是:
ClearAll[AnyTrue, AllTrue];
SetAttributes[{AnyTrue, AllTrue}, HoldAll];
Module[{exany, exall},
AnyTrue[iter : {var_Symbol, lis_List}, expr_] :=
TrueQ[Catch[Table[If[TrueQ[expr], Throw[True, exany]], iter], exany]];
AllTrue[iter : {var_Symbol, lis_List}, expr_] :=
Catch[Table[If[! TrueQ[expr], Throw[False, exall]], iter], exall] =!= False;
];
几句话解释一下:我在顶层使用模块,因为我们只需要定义一次的自定义异常标记,也可以在定义时完成。打破 Table 的方法是通过异常。不是很优雅并且对性能的影响很小,但是我们购买了由Table 完成的迭代器变量的自动动态本地化,并且很简单。为了以安全的方式执行此操作,我们必须使用唯一标记标记异常,这样我们就不会错误地捕获其他异常。我发现使用 Module 来创建持久异常标记通常是一个非常有用的技巧。现在,一些例子:
In[40]:= i = 1
Out[40]= 1
In[41]:= AnyTrue[{i, {1, 2, 3, 4, 5}}, i > 3]
Out[41]= True
In[42]:= AnyTrue[{i, {1, 2, 3, 4, 5}}, i > 6]
Out[42]= False
In[43]:= AllTrue[{i, {1, 2, 3, 4, 5}}, i > 3]
Out[43]= False
In[44]:= AllTrue[{i, {1, 2, 3, 4, 5}}, i < 6]
Out[44]= True
In[45]:= AllTrue[{a, {1, 3, 5}}, AnyTrue[{b, {2, 4, 5}}, EvenQ[a + b]]]
Out[45]= True
In[46]:= AnyTrue[{a, {1, 3, 5}}, AllTrue[{b, {2, 4, 5}}, EvenQ[a + b]]]
Out[46]= False
我从对 i 的赋值开始,以表明迭代器变量的可能全局值无关紧要 - 这是由 Table 处理的。最后,请注意(正如我在其他地方评论的那样),您对 AllTrue 和 AnyTrue 的原始签名有点过于严格,因为以下内容不起作用:
In[47]:= lst = Range[5];
AllTrue[{i, lst}, i > 3]
Out[48]= AllTrue[{i, lst}, i > 3]
(由于HoldAll 属性,lst 表示列表的事实在模式匹配时是未知的)。没有充分的理由保留此行为,因此您只需删除 _List 检查:AnyTrue[iter : {var_Symbol, lis_}, expr_] 和类似的 AllTrue,此类用例将被涵盖。