非标准评估和 PackedArray [英] Non-standard evaluation and PackedArray

查看:43
本文介绍了非标准评估和 PackedArray的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我之前询问如何制作allTrue[{x,list},test] 函数保护占位符 x 在当前上下文中不被求值,其方式与 Table[expr,{x,...}] 保护 x

I have earlier asked how to make allTrue[{x,list},test] function that protects the placeholder symbol x from evaluation in current context in the same way as Table[expr,{x,...}] protects x

我最终使用的配方间歇性地失败了,我发现问题是由列表自动转换为 PackedArrays 引起的.这是一个失败的例子

The recipe that I ended up using failed intermittently, and I found the problem down to be caused by automatic conversion of lists to PackedArrays. Here's a failing example

SetAttributes[allTrue, HoldAll];
allTrue[{var_, lis_}, expr_] := 
  LengthWhile[lis, 
    TrueQ[ReleaseHold[Hold[expr] /. HoldPattern[var] -> #]] &] == 
   Length[lis];
allTrue[{y, Developer`ToPackedArray[{1, 1, 1}]}, y > 0]

我希望 allTrue[{x,{1,2,3}},x>0] 返回 True,无论 {1,2,3} 自动转换成PackedArray,有什么更好的实现方式?

I want allTrue[{x,{1,2,3}},x>0] to return True regardless of whether {1,2,3} gets automatically converted into PackedArray, what is a better way to implement it?

推荐答案

这是我用了很长时间的一个版本(最初是为我的书的第二版写的,但我最终使用了很多).如果参数代表一些未计算的代码,那么如果我们希望将代表一个特定子句的单段代码传递给测试函数,则测试函数必须具有 HoldAllHoldFirst 属性未评估的形式(可能是也可能不是可取的).

This is a version I've been using for quite a while (wrote it for a second edition of my book originally, but I ended up using it a lot). If arguments represent some unevaluated code, then the test function must have HoldAll or HoldFirst attributes if we want a single piece of code representing one specific clause to be passed to it in its unevaluated form (which may or may not be desirable).

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 的解决方案与此解决方案非常相似.主要区别在于我关心短路和保持参数未评估(见下文),而丹尼尔只关注短路部分.

I just noticed that the solution by Daniel Reeves at the bottom of the page linked in the question, is very similar to this one. The main difference is that I care about both short-circuiting and keeping arguments unevaluated (see below), while Daniel focuses on the short-circuiting part only.

它确实有短路行为.我们需要 HoldRest 属性,因为我们希望以未评估的形式保留参数.我们还需要在纯函数中使用 HoldAll(或 HoldFirst)属性来保留每个未计算的参数,直到将其传递给 test.在 test 的主体中使用它之前是否对其进行评估现在取决于 test 的属性.举个例子:

It does have a short-circuiting behavior. We need HoldRest attribute since we want to preserve the arguments in their unevaluated form. We also need the HoldAll (or HoldFirst) attribute in a pure function to preserve each of the arguments unevaluated until it is passed to test. Whether or not it gets evaluated before it is used in the body of test depends now on the attributes of test. As an example:

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}

这是一个示例,我们将一些引起副作用(打印)的代码片段作为参数传递.最后一个参数的代码没有机会执行,因为结果已经在前面的子句中确定了:

Here is an example where we pass as arguments some pieces of code inducing side effects (printing). The code of the last argument has no chance to execute, since the result has already been determined at the previous clause:

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 示例一样).

Note that, since fastOr accepts general pieces of unevaluated code as clauses for Or, you have to wrap your list of values in Evaluate if you don't care that they are going to be evaluated at the start ( as is the case with the Range example above).

最后,我将说明 fastOr 保持代码的编程构造,以展示如何使用它(如果您愿意,可以将其视为使用保持表达式的小型速成课程).以下函数在处理保持表达式时非常有用:

Finally, I will illustrate the programmatic construction of held code for fastOr, to show how it can be used (consider it a tiny crash course on working with held expressions if you wish). The following function is very useful when working with held expressions:

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 的示例中使用的保持参数:

Here is how we use it to construct programmatically the held arguments that were used in the example with Print-s above:

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

To pass it to fastOr, we will use another useful idiom: append (or prepend) to Hold[args] until we get all function arguments, and then use Apply (note that, in general, if we don't want the piece we are appending / prepending to evaluate, we must wrap it in Unevaluated, so the general idiom looks like 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 的版本.

Regarding the original implementation you refer to, you can look at my comment to it I made some while ago. The problem is that TakeWhile and LengthWhile have bugs for packed arrays in v. 8.0.0, they are fixed in the sources of 8.0.1 - so, starting from 8.0.1, you can use either mine or Michael's version.

HTH

我刚刚注意到在您提到的帖子中,您想要一种不同的语法.虽然在这种情况下采用 fastOr 中采用的方法并不是很困难,但这里有一个不同的实现,可以说它与此特定语法的现有语言结构更接近.我建议使用 Table 和异常,因为 Table 中的迭代器接受与您想要的相同的语法.这是:

I just noticed that in the post you referred to, you wanted a different syntax. While it would not be very difficult to adopt the approach taken in fastOr to this case, here is a different implementation, which arguably is in closer correspondence with the existing language constructs for this particular syntax. I suggest to use Table and exceptions, since iterators in Table accept the same syntax as you want. Here it is:

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 来创建持久性异常标签通常是一个非常有用的技巧.现在,举一些例子:

A few words of explanation: I use Module on the top level since the custom exception tags we need to define just once, can as well do that at definition-time. The way to break out of Table is through exceptions. Not very elegant and induces a small performance hit, but we buy automatic dynamic localization of your iterator variables done by Table, and simplicity. To do this in a safe way, we must tag an exception with a unique tag, so we don't catch some other exception by mistake. I find using Module for creating persistent exception tags to be a very useful trick in general. Now, some examples:

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 处理.最后,请注意(正如我在别处评论的那样),您对 AllTrueAnyTrue 的原始签名有点过于严格,因为以下内容不起作用:

I started with an assignment to i to show that possible global values of iterator variables don't matter - this is taken care of by Table. Finally, note that (as I commented elsewhere), your original signatures for AllTrue and AnyTrue are a bit too restrictive, in the sense that the following does not work:

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,将涵盖此类用例.

(since the fact that lst represents a list is not known at the pattern-matching time, due to HoldAll attribute). There is no good reason to keep this behavior, so you can just remove the _List checks: AnyTrue[iter : {var_Symbol, lis_}, expr_] and similarly for AllTrue, and this class of use cases will be covered.

这篇关于非标准评估和 PackedArray的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆