Functions with Options

Suppose you want to create a function which has optional arguments. Maeder's book Programming in Mathematica covers the topic extensively up to version 3, but some things have changed/evolved since then.

So now what is the standard code to use for a function func[x, ...] which takes optional arguments?

-

The main change since that time seems to be that the modern way of using options is associated with OptionsPattern[] - OptionValue commands. A typical way of defining a function would be:

Options[f] = {FirstOption -> 1, SecondOption -> 2};
f[x_, y_, opts : OptionsPattern[]] :=
Print[{x, y, OptionValue[FirstOption], OptionValue[SecondOption]}]


The OptionsPattern[] is a pattern which is similar to ___?OptionQ in its purpose, but has subtle differences, some of which are discussed in this question. In the same question, it is discussed what are the major differences and advantages / disadvantages of old and new approaches. You can still use the old way though. The OptionValue command is a rather magical function, which knows which function you are in, so that you often don't have to supply the function name explicitly. However, you can always do so, since OptionValue has forms with more arguments. What you can not do is to mix the two approaches: if you declare options as ___?OptionQ, then OptionValue won't work.

The second difference is that there is new built-in functions FilterRules, which can be used to filter options. Previously, there was a package by Maeder called FilterOptions, which provided similar functionality, but was not in the widespread use, just because not everyone knew about it. The typical options filtering call looks like

g[x_, y_, opts : OptionsPattern[]] :=
f[x, y, Sequence@@FilterRules[{opts}, Options[f]]]


Filtering options is a good practice, so this addition is quite useful.

If you wanted to pass options that belong to other functions (e.g. functions that are called inside your function g) you would do something like this, and it would work even if useQ was actually an option of the function p:

g[x_, y_, opts : OptionsPattern[{g, f, p, q}]] :=
Module[
{s = If[OptionValue[useQ], q[y, FilterRules[q]], p[x, FilterRules[p]]]},
f[x, s, Sequence@@FilterRules[{opts}, Options[f]]]
]

-
Never seen FilterRules before. That is a useful tool! – Eli Lansey Jan 20 '12 at 14:44
Thank you Leonid, i was waiting for your answer :-) – magma Jan 20 '12 at 14:46
@Eli: it definitely should be a function that everybody who deals with options should know. – J. M. Jan 20 '12 at 14:52
@Eli Lansey Sometimes you need to do somthing with options, for example, pass them to some function you call: f[x,y,opts:OptionsPattern]:=g[x,y,OptionValue[FirstOption],opts], or, better, f[x,y,opts:OptionsPattern]:=g[x,y,OptionValue[FirstOption],Sequence@@FilterRule‌​s[{opts},Options[g]]]. There can be other cases when you'd like to have an explicit handle on the options passed to your function. – Leonid Shifrin Jan 20 '12 at 15:43
@Eli see my answer for a working example of opts:OptionsPattern[] – Mr.Wizard Jan 20 '12 at 16:54

Here is a practical example from a StackOverflow question. I hope that it gives a good overview of the basic methods.

Question

• What would be the best way to make a function out of the below code ?

• It would take a dataList as well as some graphical options (such as colors) as arguments and return a customized tabular representation as shown below.

overviewtheData=Text@Grid[{Map[Rotate[Text[#],
90Degree]&,data[[1]]]}~Join~data[[2;;]],
Background->{{{{White,Pink}},{1->White}}},
Dividers->{All,{1->True,2->True,0->True}},
ItemSize->{1->5,Automatic},
Alignment->Top,
Frame->True,
FrameStyle->Thickness[2],
ItemStyle->{Automatic,Automatic,{{1,1},
{1,Length@data[[1]]}}->Directive[FontSize->15,Black,Bold]}]


Goals

1. Since the main function used is Grid it makes sense to allow passing options to it.

2. You have a series of options that define your table. I want to be able to conveniently change these.

3. I want the possibility of custom options not understood by Grid.

Implementation

I will use this sample data in all examples below:

data = Prepend[
RandomInteger[99, {5, 12}],
DateString[{1, #}, "MonthName"] & /@ Range@12
];


Goal #1

An argument pattern opts:OptionsPattern[] is added, which matches any sequence of Option -> Setting arguments, and names it opts. (See: OptionsPattern for more.) Then, opts is inserted into the basic function before the other options for Grid. This allows any explicitly given options to override the defaults, or new ones to be given.

customTabular[data_, opts : OptionsPattern[]] :=
Grid[MapAt[Rotate[#, 90 Degree] & /@ # &, data, 1],
opts,
Background -> {{{White, Pink}}},
Dividers -> {All, {2 -> True}},
ItemSize -> {1 -> 5},
Alignment -> {Center, {1 -> Top}},
Frame -> True,
FrameStyle -> Thickness[2],
ItemStyle -> Directive[FontSize -> 15, Black, Bold]
] // Text


Examples:

customTabular[data]


customTabular[data, Background -> LightBlue]


Goal #2

The options that define your tabular format can be separated from the function body. This will allow them to be conveniently changed or referenced. I start by clearing the previous definition with ClearAll. Then I set default Options for customTabular:

ClearAll[customTabular]

Options[customTabular] =
{Background -> {{{White, Pink}}},
Dividers -> {All, {2 -> True}},
ItemSize -> {1 -> 5},
Alignment -> {Center, {1 -> Top}},
Frame -> True,
FrameStyle -> Thickness[2],
ItemStyle -> Directive[FontSize -> 15, Black, Bold]};


Now the function proper. Here Options@customTabular gets the rules given above.

The opts pattern is changed to OptionsPattern[{customTabular, Grid}] to declare options for either function as valid options (that is all options in either Options[customTabular] or Options[Grid]).

customTabular[data_, opts : OptionsPattern[{customTabular, Grid}]] :=
Grid[MapAt[Rotate[#, 90 Degree] & /@ # &, data, 1],
opts,
Sequence @@ Options@customTabular
] // Text


Now you can easily change the defaults with SetOptions. Example:

SetOptions[customTabular,
Background -> {{{LightMagenta, LightOrange}}}
];

customTabular[data]


Goal #3

Now I want to add an option that is not passed to Grid. I choose "Rotation" to change the text rotation of the title row.

Again I clear the prior definition and the default options. Notice the inclusion of "Rotation" -> 90 Degree in the list.

ClearAll[customTabular]

Options[customTabular] =
{Background -> {{{White, Pink}}},
Dividers -> {All, {2 -> True}},
ItemSize -> {1 -> 5},
Alignment -> {Center, {1 -> Top}},
Frame -> True,
FrameStyle -> Thickness[2],
ItemStyle -> Directive[FontSize -> 15, Black, Bold],
"Rotation" -> 90 Degree};


Now I need a way to use this new option, and I need a way to keep this option from being sent to Grid:

I first join any explicit options to the front of the Options@customTabular list, again to override defaults.

customTabular[data_, opts : OptionsPattern[{customTabular, Grid}]] :=
Grid[MapAt[Rotate[#, OptionValue["Rotation"]] & /@ # &, data, 1],
Sequence @@ FilterRules[{opts} ~Join~ Options@customTabular, Options@Grid]
] // Text


Example:

SetOptions[customTabular, Background -> {{{LightBrown, LightYellow}}}];

customTabular[data,
Dividers -> All,
"Rotation" -> -90 Degree,
FrameStyle -> {Darker@Red, Thick}
]


-
Oh man this answer helped me through hours of pain. Thanks! – Gabriel Oct 7 '12 at 19:16
@Gabriel you're very welcome! :-) Let me know if you need help getting anything specific working, or just have questions about the framework I laid out here. – Mr.Wizard Oct 7 '12 at 19:45
It seems to me that you don't need to do Sequence @@, though that may be a more recent change. Also, pretty much all of the documentation I read on this was really lacking. Maybe I was just not looking in the right places, but very little of this is explained well or very thoroughly. – ThomasH May 21 '15 at 17:08
@ThomasH Sequence @@ is needed in some cases because not all functions accept a List of rules as options; it is included here as a matter of practice. I hope you found my answer useful? I agree that the documentation is lacking but that is one reason I am here. :-) – Mr.Wizard May 21 '15 at 18:51
@Mr.Wizard I found it extremely useful. I used essentially your syntax, but I did the option Joining as a local tempopts variable that I passed, after appropriate FilterRules to everything I call. That way I can, without further additions, add more default options to the main function for any function I call or just add the options when the main function is called, and it will be sent where it is needed. – ThomasH May 21 '15 at 19:03

For additional reference, found within the file \$InstallationDirectory/SystemFiles/Kernel/TextResources/English/Messages.m are all of the defined messages for the system, and there are 11 General messages for Options that do not conform. The list is

General::optlist = "Value of option 1 -> 2 should be a list."
General::optb = "Optional object 1 in 2 is not a single blank."
General::optrs = "Option specification 1 in 2 is not a rule for a symbol or string."
General::opttf = "Value of option 1 -> 2 should be True or False."
General::optpn = "The value of 1 -> 2 should be a positive machine-sized real number."
General::opttfa = "Value of option 1 -> 2 should be True, False, or Automatic."
General::opttfna = "Value of option 1 -> 2 should be True, False, None, or Automatic."
General::optv = "Value of option 1 in 2 is not valid."
General::optvg = "Value of option 1 -> 2 should be 3."
General::optvp = "Invalid option value at position 1 in 2. Allowed values are 3."
General::optx = "Unknown option 1 in 2."


As they are General messages, they can be used with any function as follows,

Message[f::opttfa, "SomeOpt", SomeOptValue]

-
I was aware of some of those but I never bothered to find them all. Good reference. – Mr.Wizard Apr 13 '12 at 16:52
@Mr.Wizard grep General::opt Messages.m. :) – rcollyer Apr 13 '12 at 16:59

Providing optional arguments to functions is rather straight forward. I'll show you three possibilities.

1. Positional arguments

This defines a function with a second optional argument. If the argument is omitted it is taken to have the default value.

fx[list_, panel_: True] := fx0[list, panel]

2. Named arguments

This sets up a default value for a named optional argument.

Options[fx] = {panel -> True}
fx[list_, OptionsPattern[]] := fx0[list, OptionValue[panel]]


If you explicitly provide a rule for the named argument it will override the default rules stored in Options[]

fx[{1, 2, 3}, panel -> False]

3. Filtering options

If you want to provide a decorator function for a built in function and you want to pass options to the function that it calls.

myplot3D[f_, {x_, x0_, x1_}, {y_, y0_, y1_},
opts : OptionsPattern[]] :=
Plot3D[f, {x, x0, x1}, {y, y0, y1},
Evaluate[FilterRules[{opts}, Options[Plot3D]]]]


calling that without options, the default options of Plot3D are used

myplot3D[Im[ArcSin[(x + I y)^4]], {x, -2, 2}, {y, -2, 2}]


This changes the method used by Plot3D

myplot3D[Im[ArcSin[(x + I y)^4]], {x, -2, 2}, {y, -2, 2}, Mesh -> None,
PlotStyle -> Directive[Yellow, Specularity[White, 20], Opacity[0.8]],
ExclusionsStyle -> {None, Red}]


Hope that helped.

-
Thanks @rm -rf that looks much better. – Stefan Dec 19 '12 at 14:25
In my option, the code Evaluate[FilterRules[{opts}, Options[Plot3D]]] should like this Evaluate[Sequence@@FilterRules[{opts}, Options[Plot3D]]]. Could you explain it to me. Thanks. – Shutao TANG Jun 28 '15 at 7:44