可选的命名参数,而不是将它们全部包装在“OptionValue”中

时间:2011-01-13 16:53:45

标签: language-features wolfram-mathematica

假设我有一个带有可选命名参数的函数,但我坚持用他们的简单名称来引用这些参数。

考虑这个函数,它添加了两个命名参数a和b:

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将在OptionsPatternOptionValue中拥有这种魔力,它可能会一路走下去并且有一个语言结构可以正确地进行命名参数你只知道他们的名字。像命名参数的其他语言一样。 (与此同时,我很好奇可行的解决办法......)

3 个答案:

答案 0 :(得分: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的单次调用来一次获取所有值。 (主要原因是如果存在未知选项,这会减少消息。)

更新,以编程方式从选项列表中生成变量:

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

答案 1 :(得分:4)

以下是我的答案的最终版本,其中包含Brett Champion答案的贡献。

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是 - 在“运行时”。因此,它比Brett更脆弱:如果在使用def定义函数后添加一些新选项,则必须清除它并重新运行def。然而,在实践中,习惯上从ClearAll开始并将所有定义放在一个单元(单元格)中,因此这似乎不是一个真正的问题。此外,它不能使用字符串选项名称,但您的原始概念也假定它们是符号。此外,它们不应具有全局值,至少在def执行时不应具有全局值。

答案 2 :(得分: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 = 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.        *)

请注意,如果ab在调用函数时具有全局值,则此方法无效。但无论如何,Mathematica中的命名参数总是如此。