Подтвердить что ты не робот

Каковы преимущества перехода от правила и /. в 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}] (см. Раздел "Дополнительная информация" в документация). Я только недавно это сделал.

4b9b3361

Ответ 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 была намного сложнее понять при первом прохождении. Добавьте к этому четкие преимущества по времени, и я бы пошел с обновлением кода.