23

旧习惯很难改掉,我意识到我一直在使用opts___Rule模式匹配和结构,就像thisoption /. {opts} /. Options[myfunction]我目前正在开发的非常大的包中一样。Sal Manango 的“Mathematica Cookbook”提醒我,后版本 6 的做法是opts:OptionsPattern[]OptionValue[thisoption]. 无论如何,该软件包需要版本 8,但多年来我从未改变过编写此类代码的方式。

是否值得从我的 pre-version-6 做事方式中重构所有这些?有性能或其他好处吗?

问候

马鞭草属

编辑:总结

针对这个问题提出了很多好的观点,所以谢谢大家(当然还有一个)。总而言之,是的,我应该重构使用OptionsPatternand OptionValue。(注意:OptionsPattern不像OptionPattern我以前那样!)有很多原因:

  1. 触摸速度更快(@Sasha)
  2. 它更好地处理参数必须在的函数HoldForm(@Leonid)
  3. OptionsPattern自动检查您是否将有效选项传递给该函数(FilterRules如果您传递给不同的函数,仍然需要(@Leonid)
  4. 它处理RuleDelayed:>)更好(@rcollyer)
  5. Flatten它在不使用(@Andrew)的情况下处理嵌套的规则列表
  6. 使用OptionValue /@ list而不是多次调用来分配多个局部变量要容易一些someoptions /. {opts} /. Options[thisfunction](在@rcollyer和我之间的评论中出现)

编辑:7 月 25 日我最初认为,/.如果您故意从另一个函数中提取默认选项,而不是实际调用的那个,那么使用该语法可能仍然有意义。事实证明,这是通过使用其中OptionsPattern[]包含头部列表的形式来处理的,例如:(请参阅文档OptionsPattern[{myLineGraph, DateListPlot, myDateTicks, GraphNotesGrid}]中的“更多信息”部分)。我最近才解决了这个问题。

4

4 回答 4

13

似乎依赖模式匹配器比使用模式匹配器产生更快的执行速度,PatternTest因为后者需要调用评估器。无论如何,我的时间安排表明可以实现一些加速,但我认为它们并不那么重要,以至于促使重构。

In[7]:= f[x__, opts : OptionsPattern[NIntegrate]] := {x, 
  OptionValue[WorkingPrecision]}

In[8]:= f2[x__, opts___?OptionQ] := {x, 
  WorkingPrecision /. {opts} /. Options[NIntegrate]}

In[9]:= AbsoluteTiming[Do[f[1, 2, PrecisionGoal -> 17], {10^6}];]

Out[9]= {5.0885088, Null}

In[10]:= AbsoluteTiming[Do[f2[1, 2, PrecisionGoal -> 17], {10^6}];]

Out[10]= {8.0908090, Null}

In[11]:= f[1, 2, PrecisionGoal -> 17]

Out[11]= {1, 2, MachinePrecision}

In[12]:= f2[1, 2, PrecisionGoal -> 17]

Out[12]= {1, 2, MachinePrecision}
于 2011-07-07T23:36:56.193 回答
12

虽然有几个答案强调了使用选项的旧方式和新方式的不同方面,但我想提出一些额外的意见。较新的构造OptionValue-OptionsPattern 提供比 , 更多的安全性OptionQ,因为OptionValue检查全局选项列表以确保函数知道传递的选项。然而,旧的OptionQ似乎更容易理解,因为它仅基于标准模式匹配,并且与任何全局属性没有直接关系。您是否想要这些结构中的任何一个提供的这种额外安全性取决于您,但我猜大多数人觉得它很有用,尤其是对于大型项目。

这些类型检查非常有用的一个原因是,通常选项作为参数由函数以链式方式、过滤等方式传递,因此如果没有此类检查,某些模式匹配错误将很难捕获,因为它们会在“远离”其原产地的地方造成伤害。

就核心语言而言,OptionValue-OptionsPattern结构是模式匹配器的补充,也许是其所有功能中最“神奇”的。语义上没有必要,只要愿意将选项视为规则的一种特殊情况。此外,OptionValue将模式匹配连接到Options[symbol]- 一个全局属性。因此,如果一个人坚持语言纯度,那么规则opts___?OptionQ似乎更容易理解——除了标准的规则替换语义之外,不需要任何东西来理解这一点:

f[a_, b_, opts___?OptionQ] := Print[someOption/.Flatten[{opts}]/.Options[f]]

(我提醒一下,OptionQ谓词是专门为识别旧版本 Mathematica 中的选项而设计的),而这个:

f[a_, b_, opts:OptionsPattern[]] := Print[OptionValue[someOption]]

看起来很神奇。Trace当您使用并看到简短形式的计算结果为较长形式时,它会变得更加清晰OptionValue,但是它自动确定封闭函数名称的事实仍然值得注意。

OptionsPattern成为模式语言的一部分还有更多的后果。一是@Sasha 讨论的速度改进。然而,速度问题经常被过分强调(这并不是为了减损他的观察),我希望这对于带有选项的函数尤其如此,因为这些往往是更高级别的函数,可能没有琐碎的主体,大部分计算时间都将花费在其中。

另一个相当有趣的区别是何时需要将选项传递给保存其参数的函数。考虑以下示例:

ClearAll[f, ff, fff, a, b, c, d];
Options[f] = Options[ff] = {a -> 0, c -> 0};
SetAttributes[{f, ff}, HoldAll];
f[x_, y_, opts___?OptionQ] :=
   {{"Parameters:", {HoldForm[x], HoldForm[y]}}, {" options: ", {opts}}};
ff[x_, y_, opts : OptionsPattern[]] :=
   {{"Parameters:", {HoldForm[x], HoldForm[y]}}, {" options: ", {opts}}};

还行吧:

In[199]:= f[Print["*"],Print["**"],a->b,c->d]
Out[199]= {{Parameters:,{Print[*],Print[**]}},{ options: ,{a->b,c->d}}}

但是在这里,我们OptionQ基于 - 的函数将评估作为模式匹配过程的一部分泄漏:

In[200]:= f[Print["*"],Print["**"],Print["***"],a->b,c->d]
During evaluation of In[200]:= ***
Out[200]= f[Print[*],Print[**],Print[***],a->b,c->d]

这并非完全无关紧要。发生的情况是,模式匹配器为了确定匹配或不匹配的事实,必须评估第三个Print,作为评估的一部分OptionQ,因为OptionQ不包含参数。为避免评估泄漏,需要使用Function[opt,OptionQ[Unevaluated[opt]],HoldAll]. 我们没有这个问题,因为匹配的事实可以纯粹从句法上建立OptionQOptionsPattern

In[201]:= ff[Print["*"],Print["**"],a->b,c->d]
Out[201]= {{Parameters:,{Print[*],Print[**]}},{ options: ,{a->b,c->d}}}

In[202]:= ff[Print["*"],Print["**"],Print["***"],a->b,c->d]
Out[202]= ff[Print[*],Print[**],Print[***],a->b,c->d]

所以,总结一下:我认为选择一种方法而不是另一种方法很大程度上是一个品味问题——每一种方法都可以有效地使用,而且每一种方法都可以被滥用。我更倾向于使用更新的方法,因为它提供了更多的安全性,但我不排除存在一些会让你感到惊讶的极端情况——而旧方法在语义上更容易理解。这类似于 C-C++ 比较(如果这是一个合适的比较):自动化和(可能)安全性与简单性和纯度。我的两分钱。

于 2011-07-08T13:35:59.023 回答
11

一个鲜为人知(但经常有用)的事实是允许选项出现在嵌套列表中:

In[1]:= MatchQ[{{a -> b}, c -> d}, OptionsPattern[]]

Out[1]= True

诸如FilterRules之类的选项处理函数知道这一点:

In[2]:= FilterRules[{{PlotRange -> 3}, PlotStyle -> Blue, 
  MaxIterations -> 5}, Options[Plot]]

Out[2]= {PlotRange -> 3, PlotStyle -> RGBColor[0, 0, 1]}

OptionValue将其考虑在内:

In[3]:= OptionValue[{{a -> b}, c -> d}, a]

Out[3]= b

但是ReplaceAll (/.)当然没有考虑到这一点:

In[4]:= a /. {{a -> b}, c -> d}

During evaluation of In[4]:= ReplaceAll::rmix: Elements of {{a->b},c->d} are a mixture of lists and nonlists. >>

Out[4]= a /. {{a -> b}, c -> d}

因此,如果您使用OptionsPattern,您可能还应该使用 OptionValue 来确保您可以使用用户传入的选项集。

另一方面,如果您使用 ReplaceAll (/.),opts___Rule出于同样的原因,您应该坚持使用。

请注意,opts___Rule在某些(不可否​​认的)情况下,这也有点过于宽容了:

不是一个有效的选项:

In[5]:= MatchQ[Unevaluated[Rule[a]], OptionsPattern[]]

Out[5]= False

但是___Rule让它通过:

In[6]:= MatchQ[Unevaluated[Rule[a]], ___Rule]

Out[6]= True

更新:正如rcollyer指出的那样,另一个更严重的问题___Rule是它错过了使用RuleDelayed (:>)指定的选项。您可以解决它(请参阅 rcollyer 的回答),但这是使用 OptionValue 的另一个好理由。

于 2011-07-08T01:18:25.673 回答
9

您的代码本身有一个微妙但可以修复的缺陷。该模式opts___Rule与表单的选项不匹配a :> b,因此如果您需要使用它,则必须更新您的代码。直接的解决方法是替换opts___Ruleopts:(___Rule | ___RuleDelayed)which requires more typing than OptionsPattern[]. 但是,对于我们当中的懒人来说,OptionValue[...]需要比ReplaceAll. 但是,我认为它可以使阅读代码更清晰。

我发现使用OptionsPattern[]andOptionValue更容易阅读并立即理解正在做的事情。较旧的形式opts___ ...ReplaceAll在第一次通读时更难以理解。除此之外,还有明显的时间优势,我会更新你的代码。

于 2011-07-08T02:58:56.080 回答