5
$\begingroup$

Szabolcs showed in this post, that there are possibly two approaches to inherit function options. He recommended the Join approach. At first, I agreed. But today I found this approach can cause problem if we don't take care.

For example, If you have a function f with options set as

Options[f] = {...};

and you also have a bunches of function g,h,p,q,... that all inherit options from f via Join. That is

Options[g]=Join[...,Options[f]]

etc. Then what if you change the Options of f? Then you have to find all function g,h,p,q,... that inherit from f, and reevaluate their definition. This kind of evaluation dependency of function definition is kind of not reasonable.

So I turned to OptionsPattern[] approach. It doesn't have the above problem. But it got its own problems. The small problem is that SyntaxInformation needs special care, and need to use undocumented OptionNames. This one is relatively easy to tackle, here is my attempt to auto the setting process of SyntaxInformation

While the bigger problem of OptionsPattern[] approach is due to Options function. The Mathematica designed Options function to be that can only show explicit default options, not those hidden in OptionsPattern[]. For example,

ClearAll[f];
Options[f]={opt1->1};
f[x_,opts:OptionsPattern[{f,Plot}]]:=...

Evalute Options[f] can only give you {opt1->1}. However, the true available options of f should be all Plot options plus {opt1->1}. This behaviour of Options makes inheritance impossible. Because somewhere in another function g that inherit f must use FilterRules. For example

ClearAll[g];
g[y_,opts:OptionsPattern[{g,f}]]:=f[y,FilterRules[{opts},Options[f]]

However, the above definition is wrong. Because, Options[f] is incomplete!. What is more, due to Options behaviour, OptionsPattern got problems too Because according to the doc, OptionsPattern[{f}] is equivalent to Options[f], so OptionsPattern[{g,f}] is also incomplete.

So my thought is that to make OptionsPattern[] works properly. We need two additional function trueOptions and the corresponding trueOptionsPattern. The trueOptions should give all available options including those hidden in OptionsPattern, so trueOptions[f] equals correctly Plot options plus {opt1->1}. And trueOptionsPattern[f] treated as obtained from trueOptions[f]. So we can use the OptionsPattern approach to inherit.

ClearAll[g];    
g[y_,opts:trueOptionsPattern[{g,f}]]:=f[y,FilterRules[{opts},trueOptions[f]]

Unfortunately, Mathematica doesn't provide such functions

tureOptions seems easier to implement, for example

ClearAll[getOptionsPatternContent];
getOptionsPatternContent::noDownValue = 
  "`1` got no DownValue. It probably not defined";
getOptionsPatternContent[symbol_] := Module[{},
   funcForm = If[DownValues[symbol] === {},
     Message[getOptionsPatternContent::noDownValue, symbol]; Abort[],
     FullForm[
      Cases[DownValues[symbol] /. 
         Verbatim[symbol][x___] :> nullHead[x], nullHead[___], 
        Infinity][[1]]]; 
     optionsPatternContent = 
      Complement[
       Flatten@Cases[funcForm, Verbatim[OptionsPattern][x_] :> x, 
         Infinity], {symbol}]]];

ClearAll[trueOptions];
trueOptions[symbol_] := Module[{optionsPatternContent},
  optionsPatternContent = getOptionsPatternContent[symbol];
  DeleteDuplicates@
   Flatten@Join[
     If[optionsPatternContent === {}, {}, 
      Table[If[Head@i === Rule, i, trueOptions@i], {i, 
        optionsPatternContent}]], Options@symbol]]

But how to implement a trueOptionsPattern[]? A naive try

ClearAll[trueOptionsPattern];
trueOptionsPattern[] := OptionsPattern[];
trueOptionsPattern[x_] := 
  OptionsPattern[
   Flatten@Table[If[Head@i === Rule, i, trueOptions@i], {i, x}]];

is not working. Because trueOptionsPattern[{g,f}] needs g, but g is not defined yet.

What is more? OptionsPattern[] seems has some subtleties with OptionValue, so I am not sure whether this line of thought will work. Any suggestions and comments?

$\endgroup$
  • $\begingroup$ Have you seen my answer to "How do I override options with my own defaults?" question? Basically you have all options listed in Options[g], but you can still inherit default option values from f in runtime. $\endgroup$ – jkuczm Nov 20 '17 at 12:03
  • $\begingroup$ Hi, @jkuczm What if we add new options of f? $\endgroup$ – matheorem Nov 20 '17 at 12:59
  • $\begingroup$ You could use delayed assignment like Options@g := Options@f, but first call to SetOptions[g, ...] will assign an explicit list of options, so maintaining inheritance from, possibly changing, Options@f would require UpValues overriding SetOptions[g, ...]. $\endgroup$ – jkuczm Nov 20 '17 at 13:38
  • $\begingroup$ As a general note: whether you want function to inherit default values of other function is of course your design decision, but it's inheritance that increases coupling, not lack of inheritance. What you describe as "evaluation dependency of function definition" in "Join" method I see exactly opposite: as reducing dependency between default values of g and f options. Whether g calls f is an implementation detail and should not be a concern for user of g. If changing default options of f changes behavior of g, it forces users of g to keep track of those implementation details. $\endgroup$ – jkuczm Nov 20 '17 at 14:28
  • $\begingroup$ In contrast in "Join" method, changing default option values of f does not change behavior of g. To change behavior of g you need to change options of g which means that default option values of g and f are decoupled. $\endgroup$ – jkuczm Nov 20 '17 at 14:33

Your Answer

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

Browse other questions tagged or ask your own question.