Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Specifying settings for Options

How do you explicitly state the valid settings for an option? Take this example for instance

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.

enter image description here

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?

like image 989
jmlopez Avatar asked Jul 05 '11 19:07

jmlopez


People also ask

What is the difference between settings and options?

Options refers to things you may choose;you may select one over another. So, the options you chose are "preferences". Settings mostly implies the "context" that the options you chose are in and is more like configuration.

What are configuration options?

You define the configuration options in your view so that users can customize a specific instance of that view. Users see these configuration options as configuration properties in that instance. For example, the Radio Buttons view has the layout configuration option and a Layout label.

What is Option pattern?

Options Pattern is used to bind a section of configuration settings to the strongly types options classes and add it to the Asp.Net Core Dependency Injection Service Container as singleton lifetime using the "Configure" method of IServiceCollection interface.

What is services AddOptions?

AddOptions(IServiceCollection) Adds services required for using options. AddOptions<TOptions>(IServiceCollection) Gets an options builder that forwards Configure calls for the same named TOptions to the underlying service collection. AddOptions<TOptions>(IServiceCollection, String)


1 Answers

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.

like image 53
Leonid Shifrin Avatar answered Sep 18 '22 23:09

Leonid Shifrin