指定选项的设置

jml*_*pez 8 wolfram-mathematica

如何明确说明选项的有效设置?以这个例子为例

Options[myFunc] = {opt1 -> "SomeString"};
myFunc[OptionsPattern[]] := Print[OptionValue[opt1]];
Run Code Online (Sandbox Code Playgroud)

myFunc打印选项的值.如果我们评估myFunc[opt1 -> {1, 2}]然后打印{1, 2}.此功能基本上可以打印您设置的任何内容opt1.我的问题是,我怎样才能确保我的函数只接受给定数量的值opt1.我们可以从像a String和a这样简单的东西开始Integer.

为了更好地了解在给出错误值时我们期望的行为,我们opt1可以看看当我们PlotRange在函数中给出错误的值时会发生什么Plot.

在此输入图像描述

在图片中给出的示例中,我故意为PlotRange选项提供了错误的值,并给了我一条消息,指定了特定选项的正确值类型.它似乎PlotRange最终取得了默认值,因此它返回了Graphics对象.

在简单的例子中,我们想要获得的是:

myFunc::sometag : Value of option opt1 -> `1` is not a string or integer.
Run Code Online (Sandbox Code Playgroud)

怎么做到这一点?

Leo*_*rin 7

一个简单的解决方案

这是一个简单的方法:

In[304]:= ClearAll[myFunc];
Options[myFunc] = {opt1 -> "SomeString"};
myFunc::badopt = "Value of option opt1 -> `1` is not a string or integer.";
myFunc[OptionsPattern[]] :=
   With[{val = OptionValue[opt1]},
      With[{error = ! MatchQ[val, _String | _Integer]},
         If[error, Message[myFunc::badopt , val]];
         (Print[val] /; ! error)]];
Run Code Online (Sandbox Code Playgroud)

例如:

In[308]:= myFunc[opt1 -> 1]

During evaluation of In[308]:= 1

In[309]:= myFunc[opt1 -> {1, 2}]

During evaluation of In[309]:= myFunc::badopt: 
Value of option opt1 -> {1,2} is not a string or integer.

Out[309]= myFunc[opt1 -> {1, 2}]
Run Code Online (Sandbox Code Playgroud)

使用自定义赋值运算符使其一般化

我们可以使用OptionValue函数内部使用单个参数作为选项名称的事实来分解错误检查乏味.这可以通过使用mma元编程工具实现.以下是自定义赋值运算符的代码:

ClearAll[def, OptionSpecs];
SetAttributes[def, HoldAll];
def[f_[args___] :> body_,OptionSpecs[optionSpecs : {(_ ->  {_, Fail :> _}) ..}]] :=
  f[args] :=
    Module[{error = False},
      Scan[
        With[{optptrn = First[# /. optionSpecs], optval = OptionValue[#]},
          If[! MatchQ[optval, optptrn ],
             error = True;
             Return[(Fail /. Last[# /. optionSpecs])[optval]]]] &, 
        optionSpecs[[All, 1]]
      ];
      body /; ! error];
Run Code Online (Sandbox Code Playgroud)

它的作用是把一个函数定义为一个规则f_[args___]:>body_,也是规格为接受的选项设置和行动,在传递一个选项在检测到错误的执行.然后我们Scan在正文执行之前注入错误测试代码().一旦找到具有不适当设置的第一个选项,就会将错误标志设置为True,并且Fail:>code_该选项的规范部分中指定了任何代码.(_ -> {_, Fail :> _})应该读取选项规范模式(optname_ -> {optpattern_, Fail :> onerror_}),其中optname是选项名称,optpattern是选项值必须匹配的模式,并且onerror是检测到错误时要执行的任意代码.请注意,我们使用RuleDelayedin Fail:>onerror_来防止过早评估该代码.请注意,OptionSpecs为了便于阅读,添加了包装器 - 它是一个完全空闲的符号,没有附加规则.

以下是使用此自定义赋值运算符定义的函数的示例:

ClearAll[myFunc1];
Options[myFunc1] = {opt1 -> "SomeString", opt2 -> 0};
myFunc1::badopt1 = "Value of option opt1 -> `1` is not a string or integer.";
myFunc1::badopt2 =  "Value of option opt2 -> `1` is not an integer.";
def[myFunc1[OptionsPattern[]] :> 
       Print[{OptionValue[opt1], OptionValue[opt2]}],
    OptionSpecs[{
       opt1 -> {_Integer | _String, 
           Fail :> ((Message[myFunc1::badopt1, #]; Return[$Failed]) &)},
       opt2 -> {_Integer,
           Fail :> ((Message[myFunc1::badopt2, #]; Return[$Failed]) &)}}
]];
Run Code Online (Sandbox Code Playgroud)

以下是使用示例:

In[473]:= myFunc1[]
During evaluation of In[473]:= {SomeString,0}

In[474]:= myFunc1[opt2-> 10]
During evaluation of In[474]:= {SomeString,10}

In[475]:= myFunc1[opt2-> 10,opt1-> "other"]
During evaluation of In[475]:= {other,10}

In[476]:= myFunc1[opt2-> 1/2]
During evaluation of In[476]:= myFunc1::badopt2: 
Value of option opt2 -> 1/2 is not an integer.

Out[476]= $Failed

In[477]:= myFunc1[opt2-> 15,opt1->1/2]
During evaluation of In[477]:= myFunc1::badopt1: 
Value of option opt1 -> 1/2 is not a string or integer.

Out[477]= $Failed
Run Code Online (Sandbox Code Playgroud)

自动将选项检查添加到已定义的函数中

您可能也对我编写的用于测试传递选项的包感兴趣:CheckOptions在此处获得.该软件包附带一个说明其用途的笔记本.它解析函数的定义并创建其他定义以检查选项.当前的缺点(除了可能并不总是适当的新定义的产生)是它只涵盖了通过OptionQ谓词定义选项的旧方法(我还没有更新它来覆盖OptionValue - OptionsPattern.我将在这里重现附带笔记本的一部分到说明它是如何工作的:

考虑一个模型函数:

In[276]:= ClearAll[f];
f[x_, opts___?OptionQ]:= x^2;
f[x_, y_, opts___?OptionQ] := x + y;
f[x_, y_, z_] := x*y*z;
Run Code Online (Sandbox Code Playgroud)

假设我们想在选项FontSize传递给函数时返回错误消息:

In[280]:= 
f::badopt="Inappropriate option";
test[f,heldopts_Hold,heldArgs_Hold]:=(FontSize/.Flatten[List@@heldopts])=!=FontSize;
rhsF[f,__]:=(Message[f::badopt];$Failed);
Run Code Online (Sandbox Code Playgroud)

我们添加选项 - 检查定义:

In[283]:= AddOptionsCheck[f,test,rhsF]
Out[283]= {HoldPattern[f[x_,opts___?OptionQ]/;test[f,Hold[opts],Hold[x,opts]]]:>
                rhsF[f,Hold[opts],Hold[x,opts]],
           HoldPattern[f[x_,y_,opts___?OptionQ]/;test[f,Hold[opts],Hold[x,y,opts]]]:>
                rhsF[f,Hold[opts],Hold[x,y,opts]],
           HoldPattern[f[x_,opts___?OptionQ]]:>x^2,
           HoldPattern[f[x_,y_,opts___?OptionQ]]:>x+y,
           HoldPattern[f[x_,y_,z_]]:>x y z}
Run Code Online (Sandbox Code Playgroud)

如您所见,一旦我们调用AddOptionsCheck,它就会生成新的定义.它需要函数名称,测试函数和失败时执行的函数.测试函数接受主函数名称,传递给它的选项(包装Hold)和传递给它的非选项参数(也包含在内Hold).从生成的定义中,您可以看到它的作用.

我们现在检查各种输入:

In[284]:= f[3]
Out[284]= 9

In[285]:= f[3,FontWeight->Bold]
Out[285]= 9

In[286]:= f[3,FontWeight->Bold,FontSize->5]
During evaluation of In[286]:= f::badopt: Inappropriate option
Out[286]= $Failed

In[289]:= f[a,b]
Out[289]= a+b

In[290]:= f[a,b,FontWeight->Bold]
Out[290]= a+b

In[291]:= f[a,b,FontWeight->Bold,FontSize->5]
During evaluation of In[291]:= f::badopt: Inappropriate option
Out[291]= $Failed

In[292]:= OptionIsChecked[f,test]
Out[292]= True
Run Code Online (Sandbox Code Playgroud)

请注意,测试函数可以测试涉及函数名称,传递参数和传递选项的任意条件.有我的另一个包,PackageOptionChecks,可在相同的页面,其具有简单的语法以特异性测试的选项RHS,并且还可以应用到整个包.其使用的一个实际例子是另一个包PackageSymbolsDependencies,其功能的选项受到"保护" PackageOptionChecks.此外,PackageOptionChecks也可以应用于Global'上下文中的功能,没有必要具有包.

当前实现的另一个限制是我们不能返回未评估的函数.请参阅随附的笔记本中的更详细的讨论.如果对此有足够的兴趣,我会考虑更新软件包以消除我提到的一些限制.