旧习惯很难改掉,我意识到我一直在使用 opts___Rule
模式匹配和像 thisoption / 这样的结构。 {选择}/。我当前正在开发的非常大的包中的选项[myfunction]
。 Sal Manango 的“Mathematica Cookbook”提醒我,版本 6 后执行此操作的方法是 opts:OptionsPattern[]
和 OptionValue[thisoption]
。无论如何,这个包需要版本 8,但多年来我从未改变过编写此类代码的方式。
是否值得从我版本 6 之前的做事方式中重构所有这些?有性能或其他好处吗?
问候
Verbeia
编辑:总结
在回答这个问题时提出了很多好的观点,所以谢谢大家(当然还要加一)。总而言之,是的,我应该重构以使用 OptionsPattern
和 OptionValue
。 (注意:OptionsPattern
不是我之前使用的 OptionPattern
!)原因有很多:
- 速度更快 (@Sasha)
- 它可以更好地处理参数所在的函数必须在
HoldForm
(@Leonid)
-
OptionsPattern
中自动检查您是否向该函数传递了有效选项(如果您是传递给不同的函数(@Leonid)
- 它可以更好地处理
RuleDelayed
(:>
)(@rcollyer)
- 它可以在不使用
Flatten<的情况下处理嵌套的规则列表/code> (@Andrew)
- 使用 OptionValue /@ list 分配多个局部变量比多次调用 someoptions / 更容易。选项[thisfunction](在@rcollyer和我之间的评论中出现)
编辑:7月25日我最初认为使用/.
语法的一次可能会仍然有意义的是,如果您故意从另一个函数而不是实际被调用的函数中提取默认选项。事实证明,这是通过使用 OptionsPattern[]
的形式来处理的,其中包含一个头列表,例如: OptionsPattern[{myLineGraph, DateListPlot, myDateTicks, GraphNotesGrid}](请参阅 文档)。我最近才解决这个问题。
Old habits die hard, and I realise that I have been using opts___Rule
pattern-matching and constructs like thisoption /. {opts} /. Options[myfunction]
in the very large package that I'm currently developing. Sal Manango's "Mathematica Cookbook" reminds me that the post-version-6 way of doing this is opts:OptionsPattern[]
and OptionValue[thisoption]
. The package requires version 8 anyway, but I had just never changed the way I wrote this kind of code over the years.
Is it worth refactoring all that from my pre-version-6 way of doing things? Are there performance or other benefits?
Regards
Verbeia
EDIT: Summary
A lot of good points were made in response to this question, so thank you (and plus one, of course) all. To summarise, yes, I should refactor to use OptionsPattern
and OptionValue
. (NB: OptionsPattern
not OptionPattern
as I had it before!) There are a number of reasons why:
- It's a touch faster (@Sasha)
- It better handles functions where the arguments must be in
HoldForm
(@Leonid)
OptionsPattern
automatically checks that you are passing a valid option to that function (FilterRules
would still be needed if you are passing to a different function (@Leonid)
- It handles
RuleDelayed
(:>
) much better (@rcollyer)
- It handles nested lists of rules without using
Flatten
(@Andrew)
- It is a bit easier to assign multiple local variables using
OptionValue /@ list
instead of having multiple calls to someoptions /. {opts} /. Options[thisfunction]
(came up in comments between @rcollyer and me)
EDIT: 25 July I initially thought that the one time using the /.
syntax might still make sense is if you are deliberately extracting a default option from another function, not the one actually being called. It turns out that this is handled by using the form of OptionsPattern[]
with a list of heads inside it, for example: OptionsPattern[{myLineGraph, DateListPlot, myDateTicks, GraphNotesGrid}]
(see the "More information" section in the documentation). I only worked this out recently.
发布评论
评论(4)
看起来依赖模式匹配器比使用PatternTest 产生更快的执行速度,因为后者需要调用评估器。不管怎样,我的时间安排表明可以实现一些加速,但我不认为它们如此重要以至于需要进行重构。
It seems like relying on pattern-matcher yields faster execution than by using
PatternTest
as the latter entails invocation of the evaluator. Anyway, my timings indicate that some speed-ups can be achieved, but I do not think they are so critical as to prompt re-factoring.虽然几个答案都强调了新旧使用选项方式的不同方面,但我想提出一些额外的观察结果。较新的构造
OptionValue
-OptionsPattern
比OptionQ
提供更高的安全性,因为OptionValue
检查全局选项列表确保函数已知传递的选项。然而,旧的 OptionQ 似乎更容易理解,因为它仅基于标准模式匹配,并且与任何全局属性不直接相关。您是否想要这些结构提供的额外安全性取决于您,但我的猜测是大多数人发现它很有用,特别是对于较大的项目。这些类型检查真正有用的原因之一是,选项通常以链式方式作为参数通过函数传递、过滤等,因此如果没有这样的检查,一些模式匹配错误将很难捕获,因为它们会在远离其起源地的地方造成伤害。
就核心语言而言,
OptionValue
-OptionsPattern
构造是模式匹配器的补充,也许是其所有功能中最“神奇”的。只要人们愿意将选项视为规则的一种特殊情况,这在语义上就没有必要。此外,OptionValue
将模式匹配连接到Options[symbol]
- 一个全局属性。因此,如果坚持语言的纯粹性,opts___?OptionQ
中的规则似乎更容易理解——除了标准的规则替换语义之外,不需要任何东西来理解这一点:(我提醒一下
>OptionQ
谓词是专门为识别旧版本 Mathematica 中的选项而设计的),而 this:看起来很神奇。当您使用
Trace
并看到OptionValue
的简短形式计算为较长形式时,情况会变得更清晰,但它自动确定封闭函数名称的事实仍然是卓越。OptionsPattern
作为模式语言的一部分还会带来一些后果。一是@Sasha 讨论的速度改进。然而,速度问题常常被过分强调(这并不是为了偏离他的观察),我希望对于带有选项的函数来说尤其如此,因为这些往往是更高级别的函数,它们可能具有非琐碎的身体,大部分计算时间将花费在其中。另一个相当有趣的区别是,当需要将选项传递给保存其参数的函数时。考虑以下示例:
这没问题:
但是这里我们基于 OptionQ 的函数将泄漏计算作为模式匹配过程的一部分:
这并非完全微不足道。发生的情况是,为了确定匹配或不匹配的事实,模式匹配器必须评估第三个
Print
,作为OptionQ
评估的一部分,因为 < code>OptionQ 不包含参数。为了避免计算泄漏,需要使用Function[opt,OptionQ[Unevaluated[opt]],HoldAll]
代替OptionQ
。使用OptionsPattern
我们就不存在这个问题,因为匹配的事实可以纯粹从语法上建立:所以,总结一下:我认为选择一种方法而不是另一种方法很大程度上取决于品味 - 每个方法每一项都可以被有效利用,但也都可能被滥用。我更倾向于使用新的方法,因为它提供了更多的安全性,但我不排除存在一些让您惊讶的极端情况 - 而旧的方法在语义上更容易理解。这类似于 C-C++ 比较(如果这是一个合适的比较):自动化和(可能)安全性与简单性和纯粹性。我的两分钱。
While several answers have stressed different aspects of old vs. new way of using options, I'd like to make a few additional observations. The newer constructs
OptionValue
-OptionsPattern
provide more safety thanOptionQ
, sinceOptionValue
inspects a list of global Options to make sure that the passed option is known to the function. The olderOptionQ
seems however easier to understand since it is based only on the standard pattern-matching and isn't directly related to any of the global properties. Whether or not you want this extra safety provided by any of these constructs is up to you, but my guess is that most people find it useful, especially for larger projects.One reason why these type checks are really useful is that often options are passed as parameters by functions in a chain-like manner, filtered, etc., so without such checks some of the pattern-matching errors would be very hard to catch since they would be causing harm "far away" from the place of their origin.
In terms of the core language, the
OptionValue
-OptionsPattern
constructs are an addition to the pattern-matcher, and perhaps the most "magical" of all its features. It was not necessary semantically, as long as one is willing to consider options as a special case of rules. Moreover,OptionValue
connects the pattern-matching toOptions[symbol]
- a global property. So, if one insists on language purity, rules as inopts___?OptionQ
seem easier to understand - one does not need anything except the standard rule-substitution semantics to understand this:(I remind that the
OptionQ
predicate was designed specifically to recognize options in the older versions of Mathematica), while this:looks quite magical. It becomes a bit clearer when you use
Trace
and see that the short form ofOptionValue
evaluates to a longer form, but the fact that it automaticaly determines the enclosing function name is still remarkable.There are a few more consequences of
OptionsPattern
being a part of the pattern language. One is the speed improvements discussed by @Sasha. However, speed issues are often over-emphasized (this is not to detract from his observations), and I expect this to be especially true for functions with options, since these tend to be the higher-level functions, which will likely have non-trivial body, where most of the computation time will be spent.Another rather interesting difference is when one needs to pass options to a function which holds its arguments. Consider a following example:
This is ok:
But here our
OptionQ
-based function leaks evaluation as a part of pattern-matching process:This is not completely trivial. What happens is that the pattern-matcher, to establish a fact of match or non-match, must evaluate the third
Print
, as a part of evaluation ofOptionQ
, sinceOptionQ
does not hold arguments. To avoid the evaluation leak, one needs to useFunction[opt,OptionQ[Unevaluated[opt]],HoldAll]
in place ofOptionQ
. WithOptionsPattern
we don't have this problem, since the fact of the match can be established purely syntactically:So, to summarize: I think choosing one method over another is largely a matter of taste - each one can be used productively, and also each one can be abused. I am more inclined to use the newer way, since it provides more safety, but I do not exclude that there exist some corner cases when it will surprise you - while the older method is semantically easier to understand. This is something similar to C-C++ comparison (if this is an appropriate one): automation and (possibly) safety vs. simplicity and purity. My two cents.
一个鲜为人知(但经常有用)的事实是,允许选项出现在嵌套列表中:
选项处理函数,例如 FilterRules 了解这一点:
OptionValue 接受它考虑到:
但是ReplaceAll (/.) 当然不会考虑到这一点:
所以,如果您使用 OptionsPattern,您可能还应该使用 OptionValue 来确保您可以使用用户传入的选项集。
另一方面,如果您使用 ReplaceAll (/.),出于同样的原因,您应该坚持使用
opts___Rule
。请注意,
opts___Rule
在某些(诚然是晦涩的)情况下也有点过于宽容:不是一个有效的选项:
但是
___Rule
让它通过:更新:作为rcollyer 指出,
___Rule
的另一个更严重的问题是它错过了用规则延迟 (:>)。您可以解决它(请参阅 rcollyer 的答案),但这是使用 OptionValue 的另一个好理由。A little known (but frequently useful) fact is that options are allowed to appear in nested lists:
The options handling functions such as FilterRules know about this:
OptionValue takes it into account:
But ReplaceAll (/.) doesn't take this into account of course:
So, if you use OptionsPattern, you should probably also use OptionValue to ensure that you can consume the set of options the user passes in.
On the other hand, if you use ReplaceAll (/.), you should stick to
opts___Rule
for the same reason.Note that
opts___Rule
is also a little bit too forgiving in certain (admittedly obscure) cases:Not a valid option:
But
___Rule
lets it through:Update: As rcollyer pointed out, another more serious problem with
___Rule
is that it misses options specified with RuleDelayed (:>). You can work around it (see rcollyer's answer), but it's another good reason to use OptionValue.您的代码本身有一个微妙但可修复的缺陷。模式
opts___Rule
不会匹配a :>; 形式的选项。 b
,因此如果您需要使用它,则必须更新代码。立即修复的方法是将opts___Rule
替换为opts:(___Rule | ___RuleDelayed)
,这比OptionsPattern[]
需要更多的输入。但是,对于我们当中的懒人来说,OptionValue[...]
比短形式的ReplaceAll
需要更多的输入。但是,我认为这可以使代码阅读更清晰。我发现使用
OptionsPattern[]
和OptionValue
更容易阅读并立即理解正在执行的操作。旧形式的opts___ ...
和ReplaceAll
在第一次通读时更难理解。除此之外,明确的 时间优势,我会更新您的代码。Your code itself has a subtle, but fixable flaw. The pattern
opts___Rule
will not match options of the forma :> b
, so if you ever need to use it, you'll have to update your code. The immediate fix is to replaceopts___Rule
withopts:(___Rule | ___RuleDelayed)
which requires more typing thanOptionsPattern[]
. But, for the lazy among us,OptionValue[...]
requires more typing than the short form ofReplaceAll
. However, I think it makes for cleaner reading code.I find the use of
OptionsPattern[]
andOptionValue
to be easier to read and instantly comprehend what is being done. The older form ofopts___ ...
andReplaceAll
was much more difficult to comprehend on a first pass read through. Add to that, the clear timing advantages, and I'd go with updating your code.