这是我已经使用了很长一段时间的版本(最初是为我的书的第二版编写的,但我最终经常使用它)。如果参数代表一些未评估的代码,则测试函数必须具有HoldAll
or 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
(or 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
,你必须将你的值列表包装在Evaluate
如果你不关心它们将在开始时被评估(就像Range
上面的例子)。
最后,我将说明所持有代码的编程构造fastOr
,展示如何使用它(如果您愿意,可以将其视为使用保留表达式的小型速成课程)。使用保留表达式时,以下函数非常有用:
joinHeld[a___Hold] := Hold @@ Replace[Hold[a], Hold[x___] :> Sequence[x], {1}];
Example:
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
Edit:
我刚刚注意到,在您提到的帖子中,您想要不同的语法。虽然采用中采取的方法并不是很困难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
和简单性。为了以安全的方式做到这一点,我们必须使用唯一的标记来标记异常,这样我们就不会错误地捕获其他异常。我发现使用模块来创建持久异常标签通常是一个非常有用的技巧。现在,一些例子:
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
and AnyTrue
有点过于严格,因为以下内容不起作用:
In[47]:= lst = Range[5];
AllTrue[{i, lst}, i > 3]
Out[48]= AllTrue[{i, lst}, i > 3]
(因为事实上lst
表示在模式匹配时未知的列表,因为HoldAll
属性)。没有充分的理由保留此行为,因此您可以删除_List
检查:AnyTrue[iter : {var_Symbol, lis_}, expr_]
类似地对于AllTrue
,并且将涵盖此类用例。