Specifying Parameters for Parameters

How do you explicitly specify the correct parameters for the parameter? Take this example, for example

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

myFunc prints the value of the parameter. If we evaluate myFunc[opt1 -> {1, 2}] , then it prints {1, 2} . This function will essentially print everything you installed on opt1 . My question is: how can I make sure that my function accepts only a certain number of values ​​for opt1 . We can start with something simple, such as String and Integer .

To better understand the behavior that we would like, except when the wrong values ​​are given for opt1 , we can take a look at what happens when we give the wrong values ​​for PlotRange in the Plot function.

enter image description here

In the example in the figure, I intentionally gave the wrong values ​​to the PlotRange options and gave me a message indicating the correct type of values ​​for these specific parameters. It seems that PlotRange ended up accepting the default value and thus returned a Graphics object.

In a simple example, we would like to get something like:

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

Does anyone know how to achieve this?

+8
wolfram-mathematica
source share
1 answer

A simple solution

Here is an easy way:

 In[304]:= ClearAll[myFunc]; Options[myFunc] = {opt1 -> "SomeString"}; myFunc::badopt = "Value of option opt1 -> `1` is not a string or interger."; 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 interger. Out[309]= myFunc[opt1 -> {1, 2}] 

Make it common with custom assignment operators

We can use the fact that OptionValue inside a function works with a single argument, which is the name of the option, to prevent error checking. This is possible using mma meta-programming tools. Here is the code for the 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 he does is define the function as a rule f_[args___]:>body_ , as well as the specifications of the valid parameter parameters and the actions that must be performed when an error is detected in one of the transferred parameters. Then we enter the error checking code ( Scan ) before the body is executed. As soon as the first parameter with an inappropriate setting is found, the error flag will be set to True , and any code is specified in the Fail:>code_ for this option. The specification template (_ -> {_, Fail :> _}) should read (optname_ -> {optpattern_, Fail :> onerror_}) , where optname is the parameter name, optpattern is the template that must have the parameter value, and onerror - arbitrary code to execute if an error is detected. Please note that we use RuleDelayed in Fail:>onerror_ to prevent premature evaluation of this code. Note btw that the OptionSpecs wrapper was added for readability only - it is an absolutely empty character with no rules attached to it.

The following is an example of a function defined using this custom assignment operator:

 ClearAll[myFunc1]; Options[myFunc1] = {opt1 -> "SomeString", opt2 -> 0}; myFunc1::badopt1 = "Value of option opt1 -> `1` is not a string or interger."; myFunc1::badopt2 = "Value of option opt2 -> `1` is not an interger."; 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 some usage examples:

 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 interger. 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 interger. Out[477]= $Failed 

Automatically add options to already defined functions

You may also be interested in the package that I wrote to test the parameters passed: CheckOptions , here . The package comes with a laptop illustrating its use. It analyzes the definitions of your function and creates additional definitions to check the parameters. The current drawback (besides generating new definitions that may not always be suitable) is that it only covers the older way of defining options using the OptionQ predicate (I haven't updated it yet to cover OptionValue - OptionsPattern ). I will reproduce here part of a companion laptop to illustrate how this works:

Consider the 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 the FontSize option 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); 

Add an option - check 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_]]:>xyz} 

As you can see, as soon as we call AddOptionsCheck , it generates new definitions. It takes a function name, a test function, and a function to execute on failure. The test function accepts the name of the main function, the parameters passed to it (wrapped in Hold ) and the arguments passed to it without options (also wrapped in Hold ). From the generated definitions, you can see what it does.

Now we check the 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 

Note that a test function can test an arbitrary condition, including the name of the function, the arguments passed, and the parameters passed. There is another package of mine, PackageOptionChecks , available on the same page, which has simpler syntax for testing, in particular, rhs options, and can also be applied to the whole package. A practical example of its use is another package, PackageSymbolsDependencies , whose functions are "protected" from PackageOptionChecks . In addition, PackageOptionChecks can be applied to functions in a Global' context; there is no need to have a package.

Another limitation of the current implementation is that we cannot return the unevaluated function. See a more detailed discussion in the laptop accompanying the package. If there is enough interest in this, I will consider updating the package to remove some of the limitations that I mentioned.

+7
source share

All Articles