# Clean and handy Options management/filtering

### the case

I want to be able to create a function with some default options but also without need to add full explicit list of options available for it.

And then inside I want to be able to filter from given and default options, those which are Button options or Tooltip options for example.

So something like:

Options[f] = {(*list of default options*)}

f[args__, OptionsPattern[]]:=Column[{
(*Options that are suitable for Button*),
(*Options that are suitable for Tooltip*),
OptionValue[(*specific name*)]
}]


And I wasn't able to get this with built in Options management functions: OptionsPattern[], OptionValue, FilterRules etc.

1. I want to avoid Options[f] = Join[customOptions, Options[Button], ...].

I don't think is a good solution, there may be duplicates in customOptions for them and an explicit list of Options[f] grows.

2. I want to be able to provide any option to the function without error messsage e.g.: Unknown option Apparance for f...

3. We can get 2. by skipping OptionsPattern[] in definition but without it we can't use built in OptionValue. I want to be able to refer to functions by their names.

4. Rules filtering mechanism should not produce duplicates. I know Button[..., ImageSize->300, ImageSize->200] will behave stable but I find it ugly.

### my approach

(* auxiliary functions *)

mergeRules = GatherBy[Join[##], First][[All, 1]] &;

optionValue = #2 /. # &;

(* function definition *)

ClearAll[f];
Options[f] = {"Test" -> 1, ImageSize -> 100, TooltipDelay -> 20};

f[x_, optionsPattern : (_Rule | _RuleDelayed) ...] := With[{
opt = mergeRules[{optionsPattern}, Options[f]]}
,
Column@{
FilterRules[opt, Options@Button],
FilterRules[opt, Options@Tooltip],
optionValue[opt, "Test"]
}
]


So I need to start my definitions with With[{ opt = mergeRules[ {optionsPattern}, Options[f]]}, which does not seem to be a big problem, but why I have to do this?

### tests

f[1, Appearance -> "Palette"]

{Appearance->Palette, ImageSize->100}
{TooltipDelay->20}
1

f[1, ImageSize -> 200]

{ImageSize->200}
{TooltipDelay->20}
1

f[1]

{ImageSize->100}
{TooltipDelay->20}
1


### question

Is there simpler approach, with built functions maybe? Or should I include Options[Button] etc. to Options[f] and count on the fact that when given duplicates, first one wins?

### Edits

1 automatically, 2/3 by using OptionsPattern[{f,Button, ...}]. So still 4 needs custom filtering function but it is a good answer anyway.

• Related: (353), (20470) – Mr.Wizard May 5 '15 at 8:02
• FWIW I believe you could also use mergeRules = DeleteDuplicatesBy[First] (just a small comment.) – C. E. May 6 '15 at 9:44
• @Pickett Thanks for pointing this out. However, I would like to avoid functions introduced in V10 so it can be useful for more people. – Kuba May 6 '15 at 9:46
• @Pickett I forgot about that function. I guess I put it out of mind when I didn't like the performance. :-/ – Mr.Wizard May 6 '15 at 11:27
• I prefer using Options[f] = fullList. I find it annoying when I use Options[f] and get back an incomplete or empty list. – Carl Woll Mar 23 '17 at 17:34

Following your clarification this seems to be OK, though I would agree that a cleaner solution would be nice:

Options[f] = {foo -> bar, ImageSize -> 333};

f[args__, opts : OptionsPattern[{f, Button, Tooltip}]] :=
Append[
FilterRules[{opts, Options @ f}, Options @ #] & /@ {Button, Tooltip},
OptionValue[foo]
] // Column


Test:

f[1, 2, Background -> Blue, AutoAction -> False, TooltipDelay -> 1]

{Background -> RGBColor[0, 0, 1], AutoAction   -> False, ImageSize -> 333}
{Background -> RGBColor[0, 0, 1], TooltipDelay -> 1}
bar


Following your updated requirements the only streamlining I can think to recommend is to combine the functionality of your mergeRules with that of FilterRules. This is a trivial refactoring but again I hope you find some value in the idea.

getRules[base_Symbol, op___][target_Symbol] :=
First /@ GatherBy[{op, Options @ base} ~FilterRules~ Options[target], First]

Options[f] = {foo -> bar, ImageSize -> 333};

f[args__, opts : OptionsPattern[{f, Button, Tooltip}]] :=
Append[getRules[f, opts] /@ {Button, Tooltip}, OptionValue[foo]] // Column


test:

f[1, 2, Background -> Blue, AutoAction -> False, TooltipDelay -> 1, ImageSize -> 99]

{Background -> RGBColor[0, 0, 1], AutoAction   -> False, ImageSize -> 99}
{Background -> RGBColor[0, 0, 1], TooltipDelay -> 1}
bar


getRules could also be written with KeyTake

getRules[base_Symbol, op___][target_Symbol] :=
{op, Options @ base} // Flatten // KeyTake[Keys @ Options @ target] // Normal

• Should I live with Button[...., ImageSize->200, ImageSize->333]?, it is ugly but works, could it be a problem anywhere? – Kuba May 5 '15 at 8:15
• @Kuba I don't recall ever seeing that syntax produce an error, and in fact I have found internal implementations that result in duplicate rules so it seems to be accepted practice at WRI; however I added a GatherBy filter for this case in my own code here: (46925) – Mr.Wizard May 5 '15 at 8:18
• @J. M. Can you give me an example of the "not" case? – Mr.Wizard May 6 '15 at 9:22
• @J. M., @Mr.Wizard Options[f] = {a -> x}; SetOptions[f, a -> y, a -> z] uses the rightmost option. – jkuczm May 6 '15 at 16:35
• @Kuba I agree, but I was bitten by it one time. I was aggregating options like this newOpts = {opt -> val, oldOpts}. I thought f[newOpts] would work the same as SetOptions[f, newOpts];f[] (except obvious fact that the latter sets options permanently), but it doesn't. – jkuczm May 7 '15 at 11:08

Let's start with slightly modified version of mergeRules, that takes into account fact that options can have symbolic or string names and name -> val is treated the same as "name" -> val:

ClearAll[symbolToName, deleteOptionDuplicates]

symbolToName[sym_Symbol] := SymbolName[sym]
symbolToName[arg_] := arg

deleteOptionDuplicates[opts:OptionsPattern[]] :=
GatherBy[Flatten[{opts}], Composition[symbolToName, First]][[All, 1]]


Now we can define an environment, providing special option-filtering function:

ClearAll[withOptions, getOptions]
withOptions[base_Symbol, opts___] :=
Function[body,
With[{allOptions = deleteOptionDuplicates[opts, Options[base]]},
Block[{getOptions = FilterRules[allOptions, Options[#]] &},
body
]
],
HoldFirst
]


To make our environment easier to use, in function definitions, let's add some macro tricks stolen from Leonid:

withOptions /:
Verbatim[SetDelayed][lhs_, rhs : HoldPattern[withOptions[__][_]]] :=
Block[{With},
Attributes[With] = {HoldAll};
lhs := Evaluate[rhs]
]
withOptions /:
Verbatim[SetDelayed][
h_[pre___, optsPatt_OptionsPattern, post___],
HoldPattern[withOptions[body_]]
] :=
h[pre, opts : optsPatt, post] := withOptions[h, opts][body]
withOptions /:
Verbatim[SetDelayed][
h_[
pre___,
namedOptsPatt:Verbatim[Pattern][optsName_, _OptionsPattern],
post___
],
HoldPattern[withOptions[body_]]
] :=
h[pre, namedOptsPatt, post] := withOptions[h, optsName][body]


I guess that extraction of OptionsPattern from lhs could be more general.

Now we can define functions like this:

ClearAll[f]
Options[f] = {"Test" -> 1, ImageSize -> 100, TooltipDelay -> 20};
f[x_, OptionsPattern[{f, Button, Tooltip}]] :=
withOptions@Column@
{getOptions[Button], getOptions[Tooltip], OptionValue["Test"]}


It gives expected results:

f[1, Appearance -> "Palette"]

{Appearance->Palette, ImageSize->100}
{TooltipDelay->20}
1

f[1, ImageSize -> 200]

{ImageSize->200}
{TooltipDelay->20}
1

f[1]

{ImageSize->100}
{TooltipDelay->20}
1


If there are duplicates, first option is used regardless of whether it's name is a symbol or a string:

f[1, "ImageSize" -> 1, ImageSize -> 2, ImageSize -> 3]

{ImageSize->1}
{TooltipDelay->20}
1


Usually I don't need to store or return filtered options, I just want to pass them to appropriate function. In such cases other environment, that setts default option values for some functions, can be useful:

ClearAll[withDefaultOptions]
withDefaultOptions[base_Symbol, targets:{__Symbol}, opts___]:=
Function[body,
With[{allOptions=deleteOptionDuplicates[opts, Options[base]]},
InternalInheritedBlock[targets,
Scan[
SetOptions[#, FilterRules[allOptions, Options[#]]]&,
targets
];
body
]
],
HoldFirst
]
withDefaultOptions /:
Verbatim[SetDelayed][
lhs_,
rhs:HoldPattern[withDefaultOptions[_, _, ___][_]]
] :=
Block[{With},
Attributes[With]={HoldAll};
lhs := Evaluate[rhs]
]
withDefaultOptions /:
Verbatim[SetDelayed][
h_[pre___, optsPatt_OptionsPattern, post___],
HoldPattern[withDefaultOptions[body_]]
] :=
h[pre, opts:optsPatt,post] :=
withDefaultOptions[
h,
Cases[Flatten[{First[optsPatt]}], Except[h, _Symbol]],
opts
][body]
withDefaultOptions /:
Verbatim[SetDelayed][
h_[
pre___,
namedOptsPatt:Verbatim[Pattern][optsName_, optsPatt_OptionsPattern],
post___
],
HoldPattern[withDefaultOptions[body_]]
] :=
h[pre, namedOptsPatt, post] :=
withDefaultOptions[
h,
Cases[Flatten[{First[optsPatt]}], Except[h,_Symbol]],
optsName
][body]


ClearAll[f];
Options[f] = {"optA" -> "valFA", "optB" -> "valFB"};
f[OptionsPattern[]] := OptionValue[{"optA", "optB"}]


Now a function that can accept and pass options to f. It has its own options and some overridden f options, with different defaults.

ClearAll[g];
Options[g] = {"optA" -> "valGA", "optC" -> "valGC"};
g[OptionsPattern[{g, f}]] :=
withDefaultOptions@{OptionValue[{"optA", "optC"}], f[]}


Notice that we don't have to pass anything to f, in body of g. Proper default options for f are set automatically, by withDefaultOptions, based on what is matched by OptionsPattern and what is inside OptionsPattern.

If option for f is neither given explicitly, nor set as default on g, then default of f is used.

g[]
(* {{"valGA", "valGC"}, {"valGA", "valFB"}} *)

g["optA" -> 1]
(* {{1, "valGC"}, {1, "valFB"}} *)

g["optB" -> 1]
(* {{"valGA", "valGC"}, {"valGA", 1}} *)

g["optC" -> 1]
(* {{"valGA", 1}, {"valGA", "valFB"}} *)

• I'll be honest: withOptions is more fluff than I care for. However withDefaultOptions is a bloody brilliant idea! – Mr.Wizard May 7 '15 at 13:26
• @Mr.Wizard I agree concerning withOptions. Actually I had withDefaultOptions ready. withOptions was created just to show this macro/environment idea while fulfilling requirements from question. – jkuczm May 7 '15 at 13:44
• Why do you wrap Verbatim around SetDelayed? I wouldn't have expected SetDelayed to evaluate prematurely here. – bdforbes Jun 23 '15 at 2:43
• @bdforbes Honestly, I mindlessly copied it from LetL macro. Since it's also used in built-in MacrosDeclareMacro, I thought there must be some subtlety that I'm missing, but you are right that everything seems to work the same without this Verbatim. – jkuczm Jun 24 '15 at 13:50