(Posting as a self-answered question to share a technique I discovered)
In a certain project, I have many function definitions in which all parameters should be optional and come with default values. A simplified example is the following:
ClearAll[sampleFunction];
Options[sampleFunction] = {
"a" -> 1000,
"b" -> {},
"c" -> 42};
sampleFunction[OptionsPattern[]] :=
Module[{
a = OptionValue["a"],
b = OptionValue["b"],
c = OptionValue["c"]},
Append[b, a + c]];
sampleFunction[]
{1042}
sampleFunction["b" -> {1, 2, 3}, "a" -> -38]
{1, 2, 3, 4}
The problem with this pattern is there is a lot of repetitive boilerplate, increasing the risk of typographical errors and fatigue for readers and writers of the code (imagine that the option names are long and complicated and that there are dozens of them, not just three as in the example).
I'd like to have a syntax like the following,
functionator[
sampleFunction,
<|"a" -> 1000, "b" -> {}, "c" -> 42|>,
Append[b, a + c]]
that minimally captures the essential features of the definition without undue repetition.
functionator
must transform its second argument, an Association
of option names and default values, into a Module
, but it's not straightforward. For instance, the following attempt just produces syntax errors in the Module
specification because things are all being evaluated at inappropriate times:
ClearAll[functionator];
SetAttributes[functionator, HoldAllComplete];
functionator[nym_, args_, body_] :=
With[{
keys = Keys[args],
vars = Symbol /@ Keys[args],
vals = Values[args]},
ClearAll[nym];
Options[nym] = Normal[args];
nym[OptionsPattern[]] :=
Module[
MapThread[
{var, key} \[Function] var = OptionValue[key],
{vars, keys}
], body]];
functionator[
sampleFunction,
<|"a" -> 1000, "b" -> {}, "c" -> 42|>,
Append[b, a + c]]
sampleFunction[]
During evaluation of In[317]:= Module::lvlist: Local variable specification MapThread[Function[{var$,key$},var$=OptionValue[sampleFunction,{},key$]], {{a,b,c},Keys[Association[a->1000,b->{},c->42]]}] is not a List. >> Module[ MapThread[ Function[{var$, key$}, var$ = OptionValue[sampleFunction, {}, key$]], {{a, b, c}, Keys[Association["a" -> 1000, "b" -> {}, "c" -> 42]]}], Append[b, a + c]]
Trying to sort this out with various Hold...
, Release
, and Evaluate
combinations was exhausting and not converging.
There must be a better way.