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.