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.