从Rule和/切换有什么好处。在大型应用程序中的OptionsPattern []和OptionValue?

时间:2011-07-07 21:44:14

标签: wolfram-mathematica mathematica-8

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

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

此致

维尔比亚

编辑:摘要

在回答这个问题时提出了很多好处,所以谢谢(当然还有一个)。总而言之,是的,我应该重构使用OptionsPatternOptionValue。 (注意:OptionsPattern不是OptionPattern,因为我之前有过这样的原因!)原因有很多:

  1. 触摸速度更快(@Sasha)
  2. 它更好地处理参数必须在HoldForm(@ Leonid)
  3. 中的函数
  4. OptionsPattern会自动检查您是否将有效选项传递给该函数(如果要传递给其他函数,则仍然需要FilterRules
  5. 它更好地处理RuleDelayed:>)(@rcollyer)
  6. 它处理嵌套的规则列表,而不使用Flatten(@Andrew)
  7. 使用OptionValue /@ list分配多个局部变量更容易,而不是多次调用someoptions /. {opts} /. Options[thisfunction](在@rcollyer和我之间的评论中出现)
  8. 编辑:7月25日我最初认为使用/.语法的一次可能仍然有意义,如果你故意从另一个函数中提取默认选项,而不是实际的那个被召唤。事实证明,这是通过使用OptionsPattern[]的形式来处理的,其中包含头部列表,例如:OptionsPattern[{myLineGraph, DateListPlot, myDateTicks, GraphNotesGrid}](请参阅documentation中的“更多信息”部分)。我最近才解决这个问题。

4 个答案:

答案 0 :(得分: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}

答案 1 :(得分: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的另一个好理由。

答案 2 :(得分:11)

虽然有几个答案强调了旧选项和新选项使用方式的不同方面,但我还是想做一些额外的观察。较新的构造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]代替OptionQ。使用OptionsPattern我们没有这个问题,因为匹配的事实可以纯粹在语法上建立:

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 ++比较(如果这是合适的):自动化和(可能)安全性与简单性和纯度。我的两分钱。

答案 3 :(得分:9)

您的代码本身有一个微妙但可修复的缺陷。模式opts___Rulea :> b形式的选项不匹配,因此如果您需要使用它,则必须更新代码。立即解决方法是将opts___Rule替换为opts:(___Rule | ___RuleDelayed),这需要比OptionsPattern[]更多的输入。但是,对于我们之间的懒惰,OptionValue[...]需要比ReplaceAll的简短形式更多的输入。但是,我认为它使得阅读代码更加清晰。

我发现使用OptionsPattern[]OptionValue更容易阅读,并立即理解正在做的事情。在第一次阅读时,较早形式的opts___ ...ReplaceAll更难以理解。除此之外,还有明确的timing advantages,我会更新您的代码。