Functions with Options

94

77

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?

magma

Posted 2012-01-20T14:15:37.157

Reputation: 5 100

Answers

75

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]]] 
  ]

Leonid Shifrin

Posted 2012-01-20T14:15:37.157

Reputation: 108 027

Is it better to use a String or a Symbol for option names? Mma doesn't seem to have a clear preference. – Whelp – 2019-11-12T09:28:58.997

2

@Whelp Symbols are more work, but can have separate doc. pages and are more discoverable. Strings are often used for sub-options. There isn't a clear-cut advice. You can look at system functions and their options to get a picture when which are used - you will find both symbols and strings used frequently. Some arguments from here are still valid, although now I would not put as strong emphasis on symbols as I did in the past.

– Leonid Shifrin – 2019-11-12T19:14:47.103

Never seen FilterRules before. That is a useful tool! – Eli Lansey – 2012-01-20T14:44:25.530

Thank you Leonid, i was waiting for your answer :-) – magma – 2012-01-20T14:46:22.147

@Eli: it definitely should be a function that everybody who deals with options should know. – J. M.'s ennui – 2012-01-20T14:52:35.017

Oh, one question. What's the reason to use opts:OptionsPattern[]? I typically just use OptionsPattern[]. – Eli Lansey – 2012-01-20T14:57:20.783

2@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@@FilterRules[{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 – 2012-01-20T15:43:28.337

Ah, ok. Thanks! – Eli Lansey – 2012-01-20T15:48:03.163

@magma You are most welcome :). Thanks for the accept. – Leonid Shifrin – 2012-01-20T16:39:55.657

2@Eli see my answer for a working example of opts:OptionsPattern[] – Mr.Wizard – 2012-01-20T16:54:01.043

Note that Sequence@@ before FilterRules is not necessary. – faysou – 2012-01-20T17:28:24.807

@Faysal True, if you don't mind getting options as a nested list like {{Option1->1,Option2->2}}. This does not matter for OptionValue, and FilterRules will make sure that the depth of the list does not grow, but I am just used to the flat list (or sequence, when passed to a function) of options, so this is a personal preference. One of the reasons is that sometimes I also use options as rules and do some rule- substtutions with them. – Leonid Shifrin – 2012-01-20T18:00:53.130

@LeonidShifrin I hope you don't mind me adding the OptionsPattern[{g,f,p,q}] case to your answer. I didn't think it worth a separate answer but it is very useful and worth mentioning. – Verbeia – 2012-01-20T22:53:26.030

@Verbeia Yes, sure - thanks! I was thinking of adding that when writing an answer, but then thought it may be too much detail. But upon a second thought, I should have done it. – Leonid Shifrin – 2012-01-21T08:53:12.043

48

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]}]
    

enter image description here


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]

enter image description here

customTabular[data, Background -> LightBlue]

enter image description here

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]

enter image description here

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 access the option with OptionValue which will give the default if none is explicitly given.

  • I pass only valid Grid options by using FilterRules.

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}
]

enter image description here

Addendum

To add syntax highlighting to a function like this there exists a presently undocumented form; see:

Mr.Wizard

Posted 2012-01-20T14:15:37.157

Reputation: 259 163

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 – 2015-05-21T17:08:01.103

1@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 – 2015-05-21T18:51:19.983

@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 – 2015-05-21T19:03:37.370

Oh man this answer helped me through hours of pain. Thanks! – Gabriel – 2012-10-07T19:16:48.540

1@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 – 2012-10-07T19:45:24.983

1@Mr.Wizard in the final goal, do we need to pass customTabular and Grid to OptionsPattern[ ] in the customTabular function definition because it is working for me even without passing them i.e. keeping opts : OptionsPattern[] – Ali Hashmi – 2017-02-04T17:08:35.507

1@AliHashmi Good question. Given the way that I am explicitly filtering the options with FilterRules I think one could use OptionsPattern[] equally well, but I typically think of the fully qualified OptionsPattern[{symbols . . .}] as good practice. I shall consider your point and possibly revise this answer. – Mr.Wizard – 2017-02-04T17:21:39.207

19

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]

rcollyer

Posted 2012-01-20T14:15:37.157

Reputation: 32 561

1I was aware of some of those but I never bothered to find them all. Good reference. – Mr.Wizard – 2012-04-13T16:52:24.487

4@Mr.Wizard grep General::opt Messages.m. :) – rcollyer – 2012-04-13T16:59:11.517

Hi, @rcollyer. What does 1 and 2 mean? For example, if I define ClearAll[f1]; f1[opts : OptionsPattern[]] := OptionValue[a]; then Trace@f1[a->11] will get message "OptionValue::nodef,Unknown option 1 for 2.". What does it mean? – matheorem – 2017-09-29T14:25:49.197

@matheorem They're placeholders for a StringTemplate, so that additional arguments can be supplied to a Message. In your Trace you find Message[OptionValue::nodef, a, f1] where a and f1 are "slotted" into their respective placeholders in the string.

– rcollyer – 2017-09-29T15:03:08.350

@rcollyer Thank you so much, very clear explanation : ) – matheorem – 2017-09-29T15:23:26.953

15

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}]
    

    enter image description here

    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}]
    

    enter image description here

Hope that helped.

Stefan

Posted 2012-01-20T14:15:37.157

Reputation: 5 207

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. – xyz – 2015-06-28T07:44:54.730

Thanks @rm -rf that looks much better. – Stefan – 2012-12-19T14:25:18.147