Message Boards Message Boards

GROUPS:

An idea for validating a function's options

Posted 10 months ago
1900 Views
|
3 Replies
|
9 Total Likes
|

Happy New Year everyone.

After spending too much time troubleshooting bugs caused by inappropriate options, I decided it was time to find a solution that would allow me to consistently and robustly define functions that validate their options.

Of course, I started with a few Google searches and found several great ideas from this Stack Exchange from 2013. Since I knew I would want meaningful messages, my Googling also found me a Community post from 3 years ago.

It took me a while to understand these posts. To make sure I understood the concepts, I built my own solution using what I had just learned.

I would appreciate any constructive criticism the community has on this solution.

First, we need a function with options. Let us keep this simple and use:

Options[myFunction] = {
   "anInteger" -> 1
   , "aReal" -> 1.0
   , "anAssociation" -> <||>
   };

(funcName : myFunction)[x_Integer, y_Integer, 
   opt : OptionsPattern[]] := Module[
   {}
   ,
   checkOptions[funcName, opt];
   x * y
   ];

Pattern matching captures both the function name and the list of options. Then at the beginning of the function, checkOptions[] does the validation.

Then, we need checkOptions[]:

checkOptions[fn_Symbol, options___] := Check[ 
  Map[checkOption[fn, #] &, Flatten[List[options]]]
  ,
  Abort[]
  ]

For my purposes, I want to abort if any of the options fail to validate. Flatten[List[]] handles both single options as well as a list of options.

Now, we need the validation checks:

checkOption::invldopt = 
  "Option `1` for function `2` received invalid value `3`";

checkOption[myFunction, "anInteger" -> _Integer] := True;
checkOption[myFunction, "aReal" -> _?NumericQ] := True;
checkOption[myFunction, "anAssociation" -> value_Association] := True;

checkOption[fn_Symbol, opt_ -> value_] := Module[
   {},
   Message[checkOption::invldopt, fn, opt, value];
   False
   ];

Pattern matching validates the simple cases. Though, I think this solution will scale to handle more complicated validations.

Now to validate the validations:

{
 myFunction[3, 2],
 myFunction[3, 2, "anInteger" -> 5],
 myFunction[3, 2, "aReal" -> 2.5],
 myFunction[3, 2, "anInteger" -> 5, "aReal" -> 2.5],
 myFunction[1, 3 , "anInteger" -> 2, "anAssociation" -> <|"a" -> "b"|>]
 }

Works as expected without aborting. While:

myFunction[1, 3, "anInteger" -> 1.2, "anAssociation" -> 3]

Aborts after displaying two messages explaining the issues.

Again, thank you for any constructive criticisms you may have. And most importantly, have a great and safe New Year.

3 Replies

enter image description here -- you have earned Featured Contributor Badge enter image description here Your exceptional post has been selected for our editorial column Staff Picks http://wolfr.am/StaffPicks and Your Profile is now distinguished by a Featured Contributor Badge and is displayed on the Featured Contributor Board. Thank you!

Posted 10 months ago

@Martijn Froeling

Thank you for your feedback and contribution. I am working on several large applications, and efficiency gains like this have a big impact on my productivity.

Have a great and safe New Years.

Thats awesome, have been thinking of this for a while of doing it my self but never found the time to work it through. What you could do is make the option types definitions be generated automatically such that you don't have to define all the functions each time.

optionsTypes[fn_Symbol, options___] := Block[{},
  (checkOption[fn, #] := True) & /@ Flatten[List[options]]
  ]

Then you cant make the function definition easier by using

Options[myFunction] = {
   "anInteger" -> 1,
   "aReal" -> 1.0,
   "anAssociation" -> <||>
   };

optionsTypes[myFunction,
 "anInteger" -> _Integer,
 "aReal" -> _?NumericQ,
 "anAssociation" -> _Association
 ]

This will just add the definitions to the checkOption function.

In[74]:= Definition[checkOption]

checkOption[myFunction,anInteger->_Integer]:=True

checkOption[myFunction,aReal->_?NumericQ]:=True

checkOption[myFunction,anAssociation->_Association]:=True

checkOption[fn_Symbol,opt_->value_]:=Module[{},Message[checkOption::invldopt,fn,opt,value];False]

Thinking of it a bit more one could also not care about some option values so they don't need to be defined and will therefore also not need to generate an error.

optionsTypes[fn_Symbol, options___] := Block[{opts, all, undefined},
  opts = Flatten[List[options]];
  all = Options[fn];
  undefined = Complement[all[[All, 1]], opts[[All, 1]]];
  (checkOption[fn, #] := True) & /@ opts;
  (checkOption[fn, # -> _] := True) & /@ undefined;
  ]

This way this will not generate errors

Options[myFunction] = {
   "anInteger" -> 1,
   "aReal" -> 1.0,
   "anAssociation" -> <||>,
   "dontCare" -> 123
   };

optionsTypes[myFunction,
 "anInteger" -> _Integer,
 "aReal" -> _?NumericQ,
 "anAssociation" -> _Association
 ]

{myFunction[3, 2],
 myFunction[3, 2, "anInteger" -> 5],
 myFunction[3, 2, "aReal" -> 2.5],
 myFunction[3, 2, "anInteger" -> 5, "aReal" -> 2.5],
 myFunction[1, 3, "anInteger" -> 2, "anAssociation" -> <|"a" -> "b"|>],
 myFunction[1, 3, "anInteger" -> 2, "anAssociation" -> <|"a" -> "b"|>,
   "dontCare" -> 32]
 }
Reply to this discussion
Community posts can be styled and formatted using the Markdown syntax.
Reply Preview
Attachments
Remove
or Discard

Group Abstract Group Abstract