Каковы преимущества перехода от правила и /. в OptionsPattern [] и OptionValue в большом приложении?
Старые привычки умирают тяжело, и я понимаю, что я использую opts___Rule
сопоставление образцов и конструкций, таких как thisoption /. {opts} /. Options[myfunction]
в очень большом пакете, который я сейчас разрабатываю. Сал Мананго "Поваренная книга по математике" напоминает мне, что способ выполнения этой версии после версии - opts:OptionsPattern[]
и OptionValue[thisoption]
. Пакет требует версии 8 в любом случае, но я никогда не менял способ написания такого кода на протяжении многих лет.
Стоит ли рефакторинг всего того, что я делал с моей до-версии-6? Есть ли производительность или другие преимущества?
Привет
Verbeia
ИЗМЕНИТЬ: Сводка
В ответ на этот вопрос было сделано много хороших моментов, поэтому спасибо (и, плюс, конечно, все). Подводя итог, да, я должен реорганизовать использование OptionsPattern
и OptionValue
. (NB: OptionsPattern
не OptionPattern
, как это было раньше!) Существует несколько причин:
- Прикосновение быстрее (@Sasha)
- Он лучше обрабатывает функции, где аргументы должны находиться в
HoldForm
(@Leonid)
-
OptionsPattern
автоматически проверяет, что вы передаете действительный параметр этой функции (FilterRules
все равно понадобится, если вы переходите к другой функции (@Leonid)
- Он обрабатывает
RuleDelayed
(:>
) намного лучше (@rcollyer)
- Он обрабатывает вложенные списки правил без использования
Flatten
(@Andrew)
- Немного проще назначить несколько локальных переменных, используя
OptionValue /@ list
вместо нескольких вызовов на someoptions /. {opts} /. Options[thisfunction]
(появилось в комментариях между @rcollyer и мной)
EDIT: 25 июля Первоначально я думал, что одно время, использующее синтаксис /.
, может иметь смысл, если вы намеренно извлекаете параметр по умолчанию из другой функции, а не тот, который на самом деле называется, Оказывается, это обрабатывается с помощью формы OptionsPattern[]
со списком голов внутри нее, например: OptionsPattern[{myLineGraph, DateListPlot, myDateTicks, GraphNotesGrid}]
(см. Раздел "Дополнительная информация" в документация). Я только недавно это сделал.
Ответы
Ответ 1
В то время как в нескольких ответах были затронуты разные аспекты старого и нового способов использования опций, я хотел бы сделать несколько дополнительных наблюдений. Более новые конструкции 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
не содержит аргументов. Чтобы избежать утечки оценки, вместо OptionQ
следует использовать Function[opt,OptionQ[Unevaluated[opt]],HoldAll]
. С 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-С++ (если это подходит): автоматизация и (возможно) безопасность по сравнению с простотой и чистотой. Мои два цента.
Ответ 2
Похоже, что полагаться на шаблон-сопоставитель дает более быстрое выполнение, чем при использовании 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}
Ответ 3
Малоизвестный (но часто полезный) факт заключается в том, что во вложенных списках допускаются опции:
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.
Ответ 4
В вашем коде есть тонкий, но исправляемый недостаток. Шаблон opts___Rule
не будет соответствовать параметрам формы a :> b
, поэтому, если вам когда-либо понадобится его использовать, вам придется обновить свой код. Непосредственное исправление заключается в замене opts___Rule
на opts:(___Rule | ___RuleDelayed)
, который требует больше ввода, чем OptionsPattern[]
. Но для ленивого среди нас OptionValue[...]
требуется больше набрания, чем короткая форма ReplaceAll
. Тем не менее, я думаю, что это делает для более чистого кода чтения.
Я считаю, что использование OptionsPattern[]
и OptionValue
легче читать и мгновенно понимать, что делается. Старая форма opts___ ...
и ReplaceAll
была намного сложнее понять при первом прохождении. Добавьте к этому четкие преимущества по времени, и я бы пошел с обновлением кода.