8
$\begingroup$

I'm having issues with inherited options for functions I defined in a package file. Setting new default options doesn't quite work as I expect. Also, a function which I'd like to stay private to the package somehow leaks into the public context…

Consider the package file OptionInheritanceTest.m:

BeginPackage["OptionInheritanceTest`"]

f2::usage = ""
f3::usage = ""

(* Default options *)
Options[f1] = {Flag1 -> True};
Options[f2] = Join[Options[f1],
                   {Flag2 -> True}];
Options[f3] = Join[{Method -> "A"},
                   Options[f2]];

Begin["`Private`"]

f1[expr_,
   opts:OptionsPattern[]] := {expr,
                              OptionValue[Flag1]};
f2[expr_,
   opts:OptionsPattern[]] := Append[f1[expr,
                                       FilterRules[{opts},
                                                   Options[f1]]],
                                    OptionValue[Flag2]];
f3[expr_,
   opts:OptionsPattern[]] := Append[f2[expr,
                                       FilterRules[{opts},
                                                   Options[f2]]],
                                    OptionValue[Method]];

End[]

EndPackage[]

After loading this package in a notebook the commands

Options[f1]
f1[x]
Options[f2]
f2[x]
Options[f3]
f3[x]

return exactly the desired output. Let's try to set new default options for f3:

SetOptions[f3, Flag1 -> False, Flag2 -> False, Method -> "C"];

While Options[f3] perfectly reflects the new option values the command

f3[x]

yields

{x, True, True, "C"}

So the new f3 defaults are not handed down to f1 and f2 when they're called by f3. How can I achieve that?

Also, I would like f1 to remain in the private context, but Options[f1] = … seems to export it to the public context. Simply declaring this option in the private block wrecks the option inheritance unfortunately. What is the way to go forward?

$\endgroup$
6
$\begingroup$

A symbol's context is determined by the Context[] where it first appears. So, the ::usage statements for f1 and f2 are sufficient to put them into the OptionInheritanceTest` context. This means you can move the Options declarations into `Private` without bringing f2 and f3 into that context. This creates a problem, as the first appearance of Flag1 and Flag2 is now in OptionInheritanceTest`Private`. There are two solutions to this: either declare them alongside f2 and f3 (a ::usage statement is a good idea), or use string options, e.g. "Flag1" -> True, etc. The second has the advantage that you are not introducing new symbols, but the first allows you to add usage messages which is very helpful to a user.

Additionally, your method for passing options to f1 and f2 from higher in the chain is insufficient. The pattern opts:OptionsPattern[] only captures what is passed into the function, so you need to pass the entire list of options, e.g. use

FilterRules[{opts, Options[f2]}, Options[f1]]

instead of just

FilterRules[{opts}, Options[f1]]
$\endgroup$
  • $\begingroup$ Since FilterRules automatically flattens, FilterRules[{opts, Options[f2]}, Options[f1]] is marginally simpler. $\endgroup$ – jkuczm Nov 6 '15 at 15:17
  • $\begingroup$ @jkuczm good point, updated my answer. $\endgroup$ – rcollyer Nov 6 '15 at 15:27
  • $\begingroup$ "advantage that you are not introducing new symbols" - I don't think "advantage" is always the appropriate word here, but I have expressed my discontent for string options many times before. (I mean, go with it if you think it's best for you; I'm just offering a dissenting opinion.) $\endgroup$ – J. M. will be back soon Nov 6 '15 at 15:31
  • $\begingroup$ @J.M. both have their good points and bad points. A bad point for symbols is a reduction in the available symbols. Despite that, I prefer symbol options, too, as they show up in the code assist. But, string options are a way of bypassing the need of declaring them in the right context, whether that is sufficient to warrant their use depends on the user. $\endgroup$ – rcollyer Nov 6 '15 at 15:39
  • $\begingroup$ Option passing now seems to work. Is it correct to assume that opts takes precedence over Options[f2], i.e. options which are explicitly supplied to the function f2 override any defaults stored in Options[f2]? $\endgroup$ – groovybaby Nov 7 '15 at 18:53
4
$\begingroup$

Since Mathematica version 10 there's FilterOptions function from GeneralUtilities` context, that can be useful here. Using it you don't have to explicitly pass filtering pattern e.g. Options[f1] when defining f2, it will be automatically inferred from context:

Needs["GeneralUtilities`"]

ClearAll[f1, f2, Flag1, Flag2]
Options[f1] = {Flag1 -> True};
Options[f2] = Join[Options[f1], {Flag2 -> True}];
f1[expr_, opts : OptionsPattern[]] := {expr, OptionValue[Flag1]}
f2[expr_, opts : OptionsPattern[]] :=
    Append[
        f1[expr, FilterOptions[opts, Sequence @@ Options[f2]]],
        OptionValue[Flag2]
    ]

Now f2 works as expected:

SetOptions[f2, Flag1 -> False, Flag2 -> False];
f1[x]
(* {x, True} *)
f2[x]
(* {x, False, False} *)
f2[x, Flag1 -> True]
(* {x, True, False} *)
f2[x, Flag2 -> True]
(* {x, False, True} *)
f2[x, Flag1 -> True, Flag2 -> True]
(* {x, True, True} *)

You can also define your own delegating function that will automatically read default options from given symbols and automatically filter them for function in which it's used:

ClearAll[delegateOptions]

delegateOptions::usage = "\
delegateOptions[spec1, spec2, ...] \
returns a sequence of options, extracted from given speci, filtered \
for head surrounding delegateOptions expression. \
Option specification speci can be explicit opt -> val rule or delayed rule, \
a symbol from which default options will be extracted, \
or a list of valid option specifications.";

delegateOptions /: head_[args1___, delegateOptions[opts___], args2___] :=
    head[
        args1,
        Sequence @@ FilterRules[
            Replace[Flatten[{opts}], sym_Symbol :> Options[sym], {1}], 
            Options[head]
        ],
        args2
    ]

Now definition of f2 is slightly simpler instead of FilterOptions[opts, Sequence @@ Options[f2]]] you can use delegateOptions[opts, f2]:

ClearAll[f2]
Options[f2] = Join[Options[f1], {Flag2 -> True}];
f2[expr_, opts : OptionsPattern[]] :=
    Append[f1[expr, delegateOptions[opts, f2]], OptionValue[Flag2]];

f2 works the same:

SetOptions[f2, Flag1 -> False, Flag2 -> False];
f1[x]
(* {x, True} *)
f2[x]
(* {x, False, False} *)
f2[x, Flag1 -> True]
(* {x, True, False} *)
f2[x, Flag2 -> True]
(* {x, False, True} *)
f2[x, Flag1 -> True, Flag2 -> True]
(* {x, True, True} *)
$\endgroup$
  • 2
    $\begingroup$ FilterOptions[] is actually pretty old (in fact, even older than that link; it was in the first edition of Maeder's book). $\endgroup$ – J. M. will be back soon Nov 6 '15 at 15:40
  • $\begingroup$ @J.M. Thanks, I didn't know that, I first saw it as part of GeneralUtilities` . $\endgroup$ – jkuczm Nov 6 '15 at 15:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.