# How to safely forward/manage options between functions?

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 for foo1 and b->-2 for foo2 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?

Updated:

To manage this I finally have introduced a small MMA package SafeOptions

(I initially wanted to put all the details here but it was longer than expected, sorry for that).

To use the package

<<SafeOptions


you must modify foo3 as follows:

Options[foo3] = createOptionList[{a->10, b->1}, Options /@ {foo1, foo2}];

foo3[opts : OptionsPattern[]] :=
Block[{safeOpts},

If[(safeOpts = getOptionList[Options[foo3], opts]) === $$Failed, Return[$$Failed]];

foo1[1, 2, filterOptionList[Options[foo1], safeOpts]];
foo2[4, 5, filterOptionList[Options[foo2], safeOpts]];
];


Now

foo3[]


prints:

foo1 a=10 b=1

foo2 b=1

as expected. The options are correctly forwarded.

Also observe that if we do not provide the default option value b->1

Options[foo3] = createOptionList[{a->10}, Options /@ {foo1, foo2}]


an error message is generated in case of incompatible option values:

safeOptions::incompatibleOptions: Some options are incompatibles {b->{2,-2}}

\$Failed

Concerning OptionsPattern[]: the package provides the normalizeOptionPattern[] function that transforms all these different forms into a well defined list of transformation rules. Now,

foo[opts:OptionsPattern[]]:=normalizeOptionPattern[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]

{}

{}

{a -> 1, b -> 2}

{a -> 1, b -> 2}

{a -> 1, b -> 2}

{a -> 1, b -> 2}

and

foo[k_Symbol,opts:OptionsPattern[]]:=MemberQ[Keys[normalizeOptionPattern[opts]],k]


works as expected:

foo[a,{}]              (* OK *)
foo[a,{a->1,b->2}]     (* OK *)
foo[a,{{a->1,b->2}}]   (* OK *)
foo[a,a->1,b->2]       (* OK *)
`

outputs:

False

True

True

True

For further details you can visit the GitHub package page where there is a first version of the SafeOptions package. Feel free to test it/report bugs, etc.