6

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?

matheorem
  • 17,132
  • 8
  • 45
  • 115
  • 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. – jkuczm Nov 20 '17 at 12:03
  • Hi, @jkuczm What if we add new options of f? – matheorem Nov 20 '17 at 12:59
  • 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, ...]. – jkuczm Nov 20 '17 at 13:38
  • 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. – jkuczm Nov 20 '17 at 14:28
  • 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. – jkuczm Nov 20 '17 at 14:33
  • Hi, @jkuczm. I think about your words. And don't quite agree. You are right, Join reducing dependency. But this reducing is not reasonable I think. Because g calls f to run. If f is changed, then the f that g called should automatically change, and this is what normal functions behave. However, the join approach make it abnormal, and set a trap. – matheorem Nov 21 '17 at 00:46
  • If your function f is so fundamental that other functions are defined in terns of it, then why are you redefining it and adding options? If g can take any options that f takes, then surely Options[f] should be defined before Options[g]. – Jason B. Nov 21 '17 at 01:57
  • Hi, @JasonB. I think "Options[f] should be defined before Options[g]" is a good practice. But it should not be a constrain I think. – matheorem Nov 21 '17 at 02:37
  • @matheorem Whether such behavior is reasonable depends on context, I just wanted to note that I don't see behavior of "Join" method as a flaw, but more as a feature that can be used intentionally. This does not exclude existence of situations in which "OptionsPattern" method might be more reasonable. In "OptionsPattern" method, fact that f accepts also options of Plot needs to be remembered when calling f. "Can Mathematica automatically remember this fact for us?" is, I think, very good question, and this is how I read your post. – jkuczm Nov 25 '17 at 20:59
  • @jkuczm Yeah, You are right. This is what I was thinking. – matheorem Nov 26 '17 at 11:09
  • @matheorem Call me a crazy newbie lunatic (which I might be), but why not do some smart options management? For example, define all Options in one cell at the top, starting with symbols having no dependencies. Now place all definitions in any cell below them, this way Optionsare easier to manage because you can see dependencies right away, resulting in no breaks in continuity. Similarly, you can do this with definitions. It has worked for me so far, or is this a bad practice? I'm really asking! – Jules Manson Nov 19 '22 at 10:01

0 Answers0