Каковы преимущества перехода от правила и /. для OptionsPattern [] и OptionValue в большом приложении? - PullRequest
21 голосов
/ 08 июля 2011

Старые привычки тяжело умирают, и я понимаю, что использовал opts___Rule сопоставление с образцом и конструкции типа thisoption /. {opts} /. Options[myfunction] в очень большом пакете, который я сейчас разрабатываю.«Поваренная книга Mathematica» Сэла Мананго напоминает мне, что после версии 6 способ сделать это - opts:OptionsPattern[] и OptionValue[thisoption].В любом случае для этого пакета требуется версия 8, но я никогда не менял способ написания такого рода кода на протяжении многих лет.

Стоит ли рефакторинг всего этого из моего способа работы до версии 6?Есть ли производительность или другие преимущества?

С уважением

Вербея

РЕДАКТИРОВАТЬ: Резюме

Много хороших моментов было сделано вОтвет на этот вопрос, так что спасибо (и плюс один, конечно) всем.Подводя итог, да, я должен рефакторинг для использования OptionsPattern и OptionValue.(NB: 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 и мной)

РЕДАКТИРОВАТЬ: 25 июля Первоначально я думал, что одноразовое использование синтаксиса /. все еще может иметь смысл, если вы намеренно извлекаете опцию по умолчанию из другой функции, а не из фактически вызываемой.обрабатывается с использованием формы OptionsPattern[] со списком головок внутри него, например: OptionsPattern[{myLineGraph, DateListPlot, myDateTicks, GraphNotesGrid}] (см. Раздел «Дополнительная информация» в документации ).Я только недавно понял это.

Ответы [ 4 ]

13 голосов
/ 08 июля 2011

Кажется, что полагаться на сопоставление с образцом дает более быстрое выполнение, чем при использовании 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}
11 голосов
/ 08 июля 2011

Хотя в нескольких ответах подчеркиваются различные аспекты старого и нового способов использования опций, я хотел бы сделать несколько дополнительных замечаний.Более новые конструкции 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 ++ (если это уместно): автоматизация и (возможно) безопасность против простоты и чистоты.Мои два цента.

11 голосов
/ 08 июля 2011

Малоизвестный (но часто полезный) факт заключается в том, что опции могут появляться во вложенных списках:

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.

9 голосов
/ 08 июля 2011

У вашего кода есть тонкий, но исправимый недостаток.Шаблон opts___Rule не будет соответствовать параметрам формы a :> b, поэтому, если вам когда-либо понадобится его использовать, вам придется обновить свой код.Немедленное исправление заключается в замене opts___Rule на opts:(___Rule | ___RuleDelayed), что требует больше ввода, чем OptionsPattern[].Но для ленивых среди нас OptionValue[...] требует большего набора текста, чем короткая форма ReplaceAll.Тем не менее, я думаю, что это делает для более чистого чтения кода.

Я считаю, что использование OptionsPattern[] и OptionValue легче читать и мгновенно понимать, что делается.Старые формы opts___ ... и ReplaceAll было гораздо сложнее понять при первом прочтении.Добавьте к этому явные временные преимущества , и я пойду с обновлением вашего кода.

...