Необязательные именованные аргументы без переноса их всех в OptionValue

Предположим, у меня есть функция с необязательными именованными аргументами, но я настаиваю на том, чтобы ссылаться на аргументы по их неприукрашенным именам.

Рассмотрим эту функцию, которая добавляет два именованных аргумента, a и b:

Options[f] = {a->0, b->0};  (* The default values. *)
f[OptionsPattern[]] := 
  OptionValue[a] + OptionValue[b]

Как я могу написать версию этой функции, в которой последняя строка заменена просто a+b? (Представьте, что a+b — это целый набор кода.)

Ответы на следующий вопрос показывают, как сократить OptionValue (проще сказать, чем сделать), но не как полностью от него избавиться: in-mathematica">Необязательные именованные аргументы в системе Mathematica

Философское дополнение: кажется, что если Mathematica собирается иметь эту магию с OptionsPattern и OptionValue, она могла бы также пойти до конца и иметь языковую конструкцию для правильного выполнения именованных аргументов, где вы можете просто ссылаться на них, вы знаете, их имена . Как и любой другой язык с именованными аргументами. (А пока мне любопытно, какие обходные пути возможны...)


person dreeves    schedule 13.01.2011    source источник


Ответы (3)


Вот окончательная версия моего ответа, содержащая вклад Бретта Чемпиона.

ClearAll[def];
SetAttributes[def, HoldAll];
def[lhs : f_[args___] :> rhs_] /; !FreeQ[Unevaluated[lhs], OptionsPattern] :=
   With[{optionNames = Options[f][[All, 1]]},
     lhs := Block[optionNames, optionNames = OptionValue[optionNames]; rhs]];
def[lhs : f_[args___] :> rhs_] := lhs := rhs;

Причина, по которой определение дается в качестве отложенного правила в аргументе, заключается в том, что таким образом мы можем извлечь выгоду из подсветки синтаксиса. Блочный трюк используется потому, что он соответствует задаче: он не мешает возможным вложенным конструкциям лексической области видимости внутри вашей функции, и, следовательно, нет опасности непреднамеренного захвата переменной. Мы проверяем наличие OptionsPattern, так как этот код не будет корректным для определений без него, и мы хотим, чтобы def работал и в этом случае. Пример использования:

Clear[f, a, b, c, d];
Options[f] = {a -> c, b -> d};
(*The default values.*)
def[f[n_, OptionsPattern[]] :> (a + b)^n]

Теперь вы можете посмотреть на определение:

Global`f
f[n$_,OptionsPattern[]]:=Block[{a,b},{a,b}=OptionValue[{a,b}];(a+b)^n$]

f[n_,m_]:=m+n

Options[f]={a->c,b->d}

Мы можем проверить это прямо сейчас:

In[10]:= f[2]
Out[10]= (c+d)^2

In[11]:= f[2,a->e,b->q]
Out[11]= (e+q)^2

Модификации выполняются во время компиляции и довольно прозрачны. Хотя это решение позволяет сэкономить время на вводе w.r.t. У Бретта он определяет набор имен опций во время "компиляции", а у Бретта - во время выполнения. Поэтому он немного более хрупок, чем у Бретта: если вы добавите новую опцию в функцию после того, как она была определена с помощью def, вы должны очистить ее и перезапустить def. Однако на практике принято начинать с ClearAll и помещать все определения в одну часть (ячейку), так что это не кажется реальной проблемой. Кроме того, он не может работать со строковыми именами опций, но ваша исходная концепция также предполагает, что они являются символами. Кроме того, они не должны иметь глобальных значений, по крайней мере, во время выполнения def.

person Leonid Shifrin    schedule 13.01.2011
comment
Это впечатляет. Вы должны быть хакером Лиспа. Большое спасибо за помощь! Я думаю, что я согласен с ограничением, что именованные параметры не имеют глобальных значений, поскольку это также ограничение в обычном способе выполнения именованных аргументов. - person dreeves; 14.01.2011
comment
@dreeves: Lisp/Scheme/Clojure в моем списке пожеланий. Сейчас нет времени, увы. Mathematica была моим первым языком, к которому я относился серьезно. На самом деле меня тоже долгое время раздражала необходимость все время оборачивать OptionValue, но ваш вопрос побудил меня, наконец, что-то с этим сделать. Я, вероятно, начну использовать это сам. - person Leonid Shifrin; 14.01.2011
comment
@Леонид: Очень круто. Хотя теперь мне интересно, в свете обновления Бретта к его ответу, стоит ли использовать этот волосатый макрос. - person dreeves; 14.01.2011
comment
@dreeves: я только что обновил свой ответ, используя идеи из решения Бретта, теперь это намного проще, чем раньше. - person Leonid Shifrin; 14.01.2011
comment
Ах, замечательно! Хотите очистить его и избавиться от старой версии, и я отмечу этот ответ как принятый? Еще раз спасибо! - person dreeves; 14.01.2011
comment
@dreeves: почистил. Есть ли способ разделить кредит? Влияние Бретта на окончательную версию не меньше моего. - person Leonid Shifrin; 14.01.2011
comment
Я мог бы предложить вам стохастически разделить кредит путем рандомизации. :) Но нет, как вы сказали в другом месте, реальная цель - дать окончательный ответ для будущих пользователей, которые заходят на эту страницу. Основатели с самого начала поощряли кражу других ответов, чтобы сделать это. - person dreeves; 14.01.2011

Почему бы просто не использовать что-то вроде:

Options[f] = {a->0, b->0};
f[args___] := (a+b) /. Flatten[{args, Options[f]}]

Для более сложного кода я бы, вероятно, использовал что-то вроде:

Options[f] = {a->0, b->0};
f[OptionsPattern[]] := Block[{a,b}, {a,b} = OptionValue[{a,b}]; a+b]

и используйте один вызов OptionValue, чтобы получить все значения сразу. (Основная причина в том, что это сокращает количество сообщений, если присутствуют неизвестные параметры.)

Обновите, чтобы программно сгенерировать переменные из списка параметров:

Options[f] = {a -> 0, b -> 0};
f[OptionsPattern[]] := 
  With[{names = Options[f][[All, 1]]}, 
    Block[names, names = OptionValue[names]; a + b]]
person Brett Champion    schedule 13.01.2011
comment
О, классно! Одно квазиограничение, о котором я не упомянул: на практике у меня есть масса опций, и мне не хотелось бы перечислять их все явно дважды. (Ваше второе решение, которое мне, вероятно, понадобится, перечисляет их три раза больше.) - person dreeves; 14.01.2011
comment
Вы можете сгенерировать список программно. Я отредактирую свой ответ через мгновение. - person Brett Champion; 14.01.2011
comment
Это чистое решение проблемы. Однако он ломается, если кто-то пытается выполнить частичную реализацию. Например, Options[f] = {a -> 0, b -> 0}; f[OptionsPattern[]] := Block[{a, b}, a = OptionValue[a]; a + OptionValue[b]]; f[a -> 1], который возвращает сообщение об ошибке OptionValue::rep: "1->1 is not a valid replacement rule.". - person E.P.; 21.09.2015

Вот какое-то ужасное решение:

Options[f] = {a->0, b->0};
f[OptionsPattern[]] := Module[{vars, tmp, ret},
  vars = Options[f][[All,1]];
  tmp = cat[vars];
  each[{var_, val_}, Transpose[{vars, OptionValue[Automatic,#]& /@ vars}],
    var = val];
  ret = 
    a + b;  (* finally! *)
  eval["ClearAll[", StringTake[tmp, {2,-2}], "]"];
  ret]

Он использует следующие удобные функции:

cat = StringJoin@@(ToString/@{##})&;        (* Like sprintf/strout in C/C++.  *)
eval = ToExpression[cat[##]]&;              (* Like eval in every other lang. *)
SetAttributes[each, HoldAll];               (* each[pattern, list, body]      *)
each[pat_, lst_, bod_] := ReleaseHold[      (*  converts pattern to body for  *)
  Hold[Cases[Evaluate@lst, pat:>bod];]];    (*   each element of list.        *)

Обратите внимание, что это не работает, если a или b имеет глобальное значение при вызове функции. Но это всегда имело место для именованных аргументов в системе Mathematica.

person dreeves    schedule 13.01.2011
comment
пока мы на этом, позвольте мне указать, что каждая ваша функция пропускает оценку. Попробуйте так: {a, b, c} = {1, 2, 3}; каждый [{var_, val_}, {{a, 4}, {b, 5}, {c, 6}}, var = val]. Кроме того, идиома ReleaseHold[Hold[f[..,Evaluate[..],..] не работает, так как Evaluate находится слишком глубоко внутри Hold. Вот вариант, который кажется лучше: SetAttributes[each, HoldAll]; each[pat_, lst_, bod_] := Cases[Hold[lst], pat :› bod, {2}]; - person Leonid Shifrin; 13.01.2011
comment
на самом деле, даже лучше: SetAttributes[each, HoldAll]; each[pat_, lst_, bod_] := Cases[Unevaluated[lst], pat :› bod]; Или, может быть, я просто упускаю суть (назначение вашей функции)? - person Leonid Shifrin; 13.01.2011
comment
@Леонид: Спасибо! Для каждой функции см. здесь: stackoverflow.com/questions/160216/ foreach-loop-in-mathematica - person dreeves; 14.01.2011
comment
Даниил, спасибо за ссылку. К сожалению, это, похоже, распространенное заблуждение. На самом деле я не могу вспомнить ни одного случая, когда конструкция ReleaseHold[Hold[что-то]] могла бы иметь смысл в ее нынешнем виде. Это может иметь смысл: ReleaseHold[Hold[что-то]/.{некоторые правила}] (я часто использую это) или когда вы вводите что-то с помощью With. Удержание наиболее полезно для удержания выражения между несколькими оценками, а не между ними. Я обсуждал это здесь, а также некоторые сопутствующие материалы: groups.google.com/group/comp.soft-sys.math.mathematica/ - person Leonid Shifrin; 14.01.2011