Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <h2>A straightforward solution</h2> <p>Here is a simple way:</p> <pre><code>In[304]:= ClearAll[myFunc]; Options[myFunc] = {opt1 -&gt; "SomeString"}; myFunc::badopt = "Value of option opt1 -&gt; `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)]]; </code></pre> <p>For example:</p> <pre><code>In[308]:= myFunc[opt1 -&gt; 1] During evaluation of In[308]:= 1 In[309]:= myFunc[opt1 -&gt; {1, 2}] During evaluation of In[309]:= myFunc::badopt: Value of option opt1 -&gt; {1,2} is not a string or interger. Out[309]= myFunc[opt1 -&gt; {1, 2}] </code></pre> <h2>Making it general with custom assignment operators</h2> <p>We can use the fact that <code>OptionValue</code> 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:</p> <pre><code>ClearAll[def, OptionSpecs]; SetAttributes[def, HoldAll]; def[f_[args___] :&gt; body_,OptionSpecs[optionSpecs : {(_ -&gt; {_, Fail :&gt; _}) ..}]] := f[args] := Module[{error = False}, Scan[ With[{optptrn = First[# /. optionSpecs], optval = OptionValue[#]}, If[! MatchQ[optval, optptrn ], error = True; Return[(Fail /. Last[# /. optionSpecs])[optval]]]] &amp;, optionSpecs[[All, 1]] ]; body /; ! error]; </code></pre> <p>What it does is to take a function definition as a rule <code>f_[args___]:&gt;body_</code>, 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 (<code>Scan</code>) before the body gets executed. As soon as the first option with inappropriate setting is found, error flag is set to <code>True</code>, and whatever code is specified in the <code>Fail:&gt;code_</code> part of specifications for that option. The option specification pattern <code>(_ -&gt; {_, Fail :&gt; _})</code> should read <code>(optname_ -&gt; {optpattern_, Fail :&gt; onerror_})</code>, where <code>optname</code> is an option name, <code>optpattern</code> is a pattern that the option value must match, and <code>onerror</code> is arbitrary code to execute if error is detected. Note that we use <code>RuleDelayed</code> in <code>Fail:&gt;onerror_</code>, to prevent premature evaluation of that code. Note b.t.w. that the <code>OptionSpecs</code> wrapper was added solely for readability - it is a completely idle symbol with no rules attached to it.</p> <p>Here is an example of a function defined with this custom assignment operator:</p> <pre><code>ClearAll[myFunc1]; Options[myFunc1] = {opt1 -&gt; "SomeString", opt2 -&gt; 0}; myFunc1::badopt1 = "Value of option opt1 -&gt; `1` is not a string or interger."; myFunc1::badopt2 = "Value of option opt2 -&gt; `1` is not an interger."; def[myFunc1[OptionsPattern[]] :&gt; Print[{OptionValue[opt1], OptionValue[opt2]}], OptionSpecs[{ opt1 -&gt; {_Integer | _String, Fail :&gt; ((Message[myFunc1::badopt1, #]; Return[$Failed]) &amp;)}, opt2 -&gt; {_Integer, Fail :&gt; ((Message[myFunc1::badopt2, #]; Return[$Failed]) &amp;)}} ]]; </code></pre> <p>Here are examples of use:</p> <pre><code>In[473]:= myFunc1[] During evaluation of In[473]:= {SomeString,0} In[474]:= myFunc1[opt2-&gt; 10] During evaluation of In[474]:= {SomeString,10} In[475]:= myFunc1[opt2-&gt; 10,opt1-&gt; "other"] During evaluation of In[475]:= {other,10} In[476]:= myFunc1[opt2-&gt; 1/2] During evaluation of In[476]:= myFunc1::badopt2: Value of option opt2 -&gt; 1/2 is not an interger. Out[476]= $Failed In[477]:= myFunc1[opt2-&gt; 15,opt1-&gt;1/2] During evaluation of In[477]:= myFunc1::badopt1: Value of option opt1 -&gt; 1/2 is not a string or interger. Out[477]= $Failed </code></pre> <h2>Adding option checks to already defined functions automatically</h2> <p>You might also be interested in a package I wrote to test the passed options: <code>CheckOptions</code>, available <a href="http://www.mathprogramming-intro.org/additional_resources.html" rel="nofollow">here</a>. 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 <code>OptionQ</code> predicate (I did not yet update it to cover <code>OptionValue - OptionsPattern</code>. I will reproduce here a part of the accompanying notebook to illustrate how it works:</p> <p>Consider a model function :</p> <pre><code>In[276]:= ClearAll[f]; f[x_, opts___?OptionQ]:= x^2; f[x_, y_, opts___?OptionQ] := x + y; f[x_, y_, z_] := x*y*z; </code></pre> <p>Suppose we want to return an error message when an option <code>FontSize</code> is passed to our function :</p> <pre><code>In[280]:= f::badopt="Inappropriate option"; test[f,heldopts_Hold,heldArgs_Hold]:=(FontSize/.Flatten[List@@heldopts])=!=FontSize; rhsF[f,__]:=(Message[f::badopt];$Failed); </code></pre> <p>We add the option - checking definitions :</p> <pre><code>In[283]:= AddOptionsCheck[f,test,rhsF] Out[283]= {HoldPattern[f[x_,opts___?OptionQ]/;test[f,Hold[opts],Hold[x,opts]]]:&gt; rhsF[f,Hold[opts],Hold[x,opts]], HoldPattern[f[x_,y_,opts___?OptionQ]/;test[f,Hold[opts],Hold[x,y,opts]]]:&gt; rhsF[f,Hold[opts],Hold[x,y,opts]], HoldPattern[f[x_,opts___?OptionQ]]:&gt;x^2, HoldPattern[f[x_,y_,opts___?OptionQ]]:&gt;x+y, HoldPattern[f[x_,y_,z_]]:&gt;x y z} </code></pre> <p>As you can see, once we call <code>AddOptionsCheck</code>, 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 <code>Hold</code>), and non-options arguments passed to it (also wrapped in <code>Hold</code>). From the generated definitions you can see what is does.</p> <p>We now check on various inputs :</p> <pre><code>In[284]:= f[3] Out[284]= 9 In[285]:= f[3,FontWeight-&gt;Bold] Out[285]= 9 In[286]:= f[3,FontWeight-&gt;Bold,FontSize-&gt;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-&gt;Bold] Out[290]= a+b In[291]:= f[a,b,FontWeight-&gt;Bold,FontSize-&gt;5] During evaluation of In[291]:= f::badopt: Inappropriate option Out[291]= $Failed In[292]:= OptionIsChecked[f,test] Out[292]= True </code></pre> <p>Please note that the test function can test for <em>arbitrary</em> condition involving function name, passed arguments and passed options. There is another package of mine, <code>PackageOptionChecks</code>, 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, <code>PackageSymbolsDependencies</code>, whose functions' options are "protected" by <code>PackageOptionChecks</code>. Also, <code>PackageOptionChecks</code> may be applied to functions in <code>Global'</code> context as well, it is not necessary to have a package.</p> <p>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.</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload