开发者

Specifying settings for Options

How do you explicitly state the valid settings for an option? Take this example for in开发者_如何转开发stance

Options[myFunc] = {opt1 -> "SomeString"};
myFunc[OptionsPattern[]] := Print[OptionValue[opt1]];

myFunc prints the value of the option. If we evaluate myFunc[opt1 -> {1, 2}] then it prints {1, 2}. This function will essentially print anything that you set to opt1. My question is, how can I make sure that my function only accept a given number of values for opt1. We can start with something simple like a String and an Integer.

To have a better idea of the behavior that we would expect when given the wrong values for opt1 we can take a look at what happens when we give the wrong values for PlotRange in the function Plot.

Specifying settings for Options

In the example given in the picture I purposely gave wrong values to the PlotRange options and gave me a message specifying the correct type of values for that particular options. It seems that it PlotRange ended up taking its default value and thus it return the Graphics object.

In the simple example what we would like to obtain is something like:

myFunc::sometag : Value of option opt1 -> `1` is not a string or integer.

How to achieve this?


A straightforward solution

Here is a simple way:

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

For example:

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

Making it general with custom assignment operators

We can use the fact that OptionValue inside a function works with a single argument being an option name, to factor out the error-checking tedium. This is possible by using mma meta-programming facilities. Here is the code for a custom assignment operator:

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

What it does is to take a function definition as a rule f_[args___]:>body_, and also the specifications for the acceptable options settings and actions to perform upon detection of an error in one of the passed options. We then inject the error-testing code (Scan) before the body gets executed. As soon as the first option with inappropriate setting is found, error flag is set to True, and whatever code is specified in the Fail:>code_ part of specifications for that option. The option specification pattern (_ -> {_, Fail :> _}) should read (optname_ -> {optpattern_, Fail :> onerror_}), where optname is an option name, optpattern is a pattern that the option value must match, and onerror is arbitrary code to execute if error is detected. Note that we use RuleDelayed in Fail:>onerror_, to prevent premature evaluation of that code. Note b.t.w. that the OptionSpecs wrapper was added solely for readability - it is a completely idle symbol with no rules attached to it.

Here is an example of a function defined with this custom assignment operator:

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]) &)}}
]];

Here are examples of use:

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

Adding option checks to already defined functions automatically

You might also be interested in a package I wrote to test the passed options: CheckOptions, available here. The package comes with a notebook illustrating its use. It parses the definitions of your function and creates additional definitions to check the options. The current downside (apart from generation of new definitions which may not always be appropriate) is that it only covers older way to define options through OptionQ predicate (I did not yet update it to cover OptionValue - OptionsPattern. I will reproduce here a part of the accompanying notebook to illustrate how it works:

Consider a model function:

In[276]:= ClearAll[f];
f[x_, opts___?OptionQ]:= x^2;
f[x_, y_, opts___?OptionQ] := x + y;
f[x_, y_, z_] := x*y*z;

Suppose we want to return an error message when an option FontSize is passed to our function:

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

We add the option - checking definitions:

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}

As you can see, once we call AddOptionsCheck, it generates new definitions. It takes the function name, the testing function, and the function to execute on failure. The testing function accepts the main function name, options passed to it (wrapped in Hold), and non-options arguments passed to it (also wrapped in Hold). From the generated definitions you can see what is does.

We now check on various inputs:

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

Please note that the test function can test for arbitrary condition involving function name, passed arguments and passed options. There is another package of mine, PackageOptionChecks, available at the same page, which has a simpler syntax to test specifically r.h.s. of options, and can also be applied to entire package. A practical example of its use is yet another package, PackageSymbolsDependencies, whose functions' options are "protected" by PackageOptionChecks. Also, PackageOptionChecks may be applied to functions in Global' context as well, it is not necessary to have a package.

One other limitation of the current implementation is that we can not return the function unevaluated. Please see a more detailed discussion in the notebook accompanying the package. If there is enough interest in this, I will consider updating the package to remove some of the limitations I mentioned.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜