2011-01-13 7 views
4

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

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

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

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

Ответы на следующий вопрос показывают, как сокращайте OptionValue (проще сказать, чем сделать), но не так, как от нее избавиться совсем: Optional named arguments in Mathematica

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

ответ

4

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

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. Brett's, он определяет набор имен опций в «время компиляции», а Brett's - в «run-time». Таким образом, он немного более хрупкий, чем Brett's: если вы добавите новую функцию в функцию после того, как она была определена с помощью def, вы должны очистить ее и повторить def. На практике, однако, принято начинать с ClearAll и помещать все определения в одну часть (ячейку), поэтому это не кажется реальной проблемой. Кроме того, он не может работать со строковыми именами параметров, но ваша оригинальная концепция также предполагает, что они являются символами. Кроме того, они не должны иметь глобальных значений, по крайней мере, не в то время, когда выполняется def.

+0

Это впечатляет. Вы должны быть хакером Lisp. Большое спасибо за помощь! Я думаю, что я в порядке с ограничением, что именованные параметры не имеют глобальных значений, поскольку это ограничение в обычном способе именованных аргументов. – dreeves

+0

@dreeves: Lisp/Scheme/Clojure находится в моем списке желаний. Увы, нет времени. Mathematica был моим первым языком, который я получил seriosly. На самом деле я тоже долгое время раздражался необходимостью обертывания OptionValue все время, но ваш вопрос побудил меня, наконец, что-то сделать. Я, скорее всего, начну использовать это сам. –

+0

@ Leonid: Очень круто. Хотя теперь мне интересно, в свете обновления Бретта к его ответу, если стоит использовать этот волосатый макрос. – dreeves

0

Вот вид ужасающей решения:

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 = [email protected]@(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[[email protected], pat:>bod];]]; (* each element of list.  *) 

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

+0

пока мы на нем, позвольте мне указать, что ваша «каждая» функция утечки оценки. Попробуйте следующее: {a, b, c} = {1, 2, 3}; каждый [{var_, val_}, {{a, 4}, {b, 5}, {c, 6}}, var = val]. Кроме того, идиома ReleaseHold [Hold [f [.., Evaluate [..], ..] не работает, так как Evaluate слишком глубоко внутри Hold. Вот версия, которая выглядит лучше: SetAttributes [каждый, HoldAll]; каждый [pat_, lst_, bod_]: = Случаи [Удерживать [lst], pat:> bod, {2}]; –

+0

на самом деле, еще лучшая версия: SetAttributes [each, HoldAll]; каждый [pat_, lst_, bod_]: = Случаи [Unevaluated [lst], pat:> bod]; Или, может быть, я просто пропущу точку (цель вашей функции)? –

+0

@Leonid: Спасибо! Для «каждой» функции см. Здесь: http://stackoverflow.com/questions/160216/foreach-loop-in-mathematica – dreeves

6

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

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, чтобы получить все значения в один раз. (Основная причина в том, что это сокращает сообщения, если есть неизвестные опции присутствуют.)

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

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

О, хорошо! Один квазиконстант я не упомянул: на практике у меня много вариантов, и мне не нужно было перечислять их все явно дважды. (Ваше второе решение, которое мне, вероятно, понадобится, перечисляет три дополнительных раза). – dreeves

+0

Вы можете сгенерировать список программно. Я отредактирую свой ответ через мгновение. –

+0

Красивая! Спасибо! – dreeves

 Смежные вопросы

  • Нет связанных вопросов^_^