【问题标题】:Optional named arguments without wrapping them all in "OptionValue"可选的命名参数,而不将它们全部包装在“OptionValue”中
【发布时间】:2011-06-08 15:19:52
【问题描述】:

假设我有一个带有可选命名参数的函数,但我坚持通过它们的朴素名称来引用这些参数。

考虑这个添加了两个命名参数 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 将拥有 OptionsPattern 和 OptionValue 的魔力,它还不如一路走下去,并有一个语言结构来正确处理命名参数,你可以在其中引用它们你知道,他们的名字。就像所有其他具有命名参数的语言一样。 (与此同时,我很好奇有什么变通办法……)

【问题讨论】:

    标签: language-features wolfram-mathematica


    【解决方案1】:

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

    【讨论】:

    • 这令人印象深刻。你一定是个 Lisp 黑客。非常感谢你的帮忙!我认为我可以接受命名参数没有全局值的限制,因为这也是执行命名参数的正常方式的限制。
    • @dreeves:Lisp/Scheme/Clojure 在我的愿望清单上。现在没时间了,唉。 Mathematica 是我认真学习的第一门语言。实际上,我也一直因为需要一直包装 OptionValue 而烦恼了很长时间,但是您的问题最终促使我对此采取了一些措施。我可能会自己开始使用它。
    • @Leonid:非常酷。虽然我现在想知道,鉴于布雷特对他的回答的更新,是否值得使用这个毛茸茸的宏。
    • @dreeves:我刚刚使用 Brett 解决方案中的想法更新了我的答案,现在比以前简单多了
    • 啊,太好了!想要清理它并摆脱旧版本,我会将此标记为已接受的答案?再次感谢!
    【解决方案2】:

    为什么不直接使用类似的东西:

    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]]
    

    【讨论】:

    • 哦,太好了!我没有提到一个准约束:在实践中,我有很多选择,并且希望不必将它们都明确列出两次。 (您的第二个解决方案——我可能需要它——将它们额外列出了三遍。)
    • 您可以通过编程方式生成列表。我稍后会编辑我的答案。
    • 这是一个干净的解决方案。但是,如果尝试进行部分实现,它就会中断。例如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."
    【解决方案3】:

    这是一种可怕的解决方案:

    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 中的命名参数,情况总是如此。

    【讨论】:

    • 在我们讨论的时候,让我指出您的“每个”函数会泄漏评估。试试这个:{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}];
    • 其实更好的版本:SetAttributes[each, HoldAll];每个[pat_, lst_, bod_] := 案例[未评估[lst], pat :> bod];或者,可能是,我只是错过了重点(你的功能的目的)?
    • @Leonid:谢谢!对于“每个”功能,请参见此处:stackoverflow.com/questions/160216/foreach-loop-in-mathematica
    • 丹尼尔,感谢您的链接。不幸的是,这似乎是一个普遍的误解。实际上,我想不出任何构造 ReleaseHold[Hold[something]] 可能有意义的情况,就目前而言。这可能是有意义的:ReleaseHold[Hold[something]/.{some rules}](我经常使用这个),或者当你用 With 注入一些东西时。保持对于在多个评估之间保持表达式保持最有用,而不是单个评估。我在这里讨论了它,以及一些相关的东西:groups.google.com/group/comp.soft-sys.math.mathematica/…
    猜你喜欢
    • 1970-01-01
    • 2016-02-04
    • 1970-01-01
    • 1970-01-01
    • 2017-12-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-04
    相关资源
    最近更新 更多