Algorithms can often be splitted into smaller and reusable functions (that's a good thing).
However with this scheme it is not clear to me how to ensure the consistency of the option values and their proper forwarding across functions.
Imagine that you have 2 functions: foo1
and foo2
, where a,b
are your algorithm parameters:
Options[foo1] = {a -> 1, b -> 2};
foo1[arg__, opts : OptionsPattern[]] :=
Print["foo1 a=", OptionValue[a], " b=", OptionValue[b]];
Options[foo2] = {b -> -2};
foo2[arg__, opts : OptionsPattern[]] :=
Print["foo2 b=", OptionValue[b]];
Now you want to define a foo3
function that reuses foo1
and foo2
Options[foo3] = {a -> 10, c -> 4};
foo3[opts : OptionsPattern[]] := Block[{},
foo1[1, 2, FilterRules[{opts}, Options[foo1]]];
foo2[4, 5, FilterRules[{opts}, Options[foo2]]];
];
Running this example:
foo3[]
prints:
foo1 a=1 b=2
foo2 b=-2
IMHO there are two main issues:
- the
a->10
default value is not forwarded - incompatible option
b->2
forfoo1
andb->-2
forfoo2
is not detected
Another problem is that OptionsPattern[]
is very permissive:
foo[opts:OptionsPattern[]]:=opts
foo[2]
foo[{}]
foo[{{}}]
foo[a -> 1, b -> 2]
foo[{a -> 1, b -> 2}]
foo[{{a -> 1, b -> 2}}]
foo[{{{a -> 1, b -> 2}}}]
prints:
foo[2]
{}
{{}}
Sequence[a -> 1, b -> 2]
{a -> 1, b -> 2}
{{a -> 1, b -> 2}}
{{{a -> 1, b -> 2}}}
This makes options processing tedious, for instance checking if an option with key k
is present is not as simple as it should be. The code below
foo[k_Symbol,opts:OptionsPattern[]]:=MemberQ[Keys[opts],k]
does not work as expected:
foo[a,{}] (* OK *)
foo[a,{a->1,b->2}] (* OK *)
foo[a,{{a->1,b->2}}] (* Problem *)
foo[a,a->1,b->2] (* Problem *)
outputs:
False
True
False <- Should be True
False <- Should be True
How to fix these problems?