97
$\begingroup$

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?

$\endgroup$

4 Answers 4

78
$\begingroup$

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]]] 
  ]
$\endgroup$
14
  • $\begingroup$ Never seen FilterRules before. That is a useful tool! $\endgroup$
    – Eli Lansey
    Commented Jan 20, 2012 at 14:44
  • $\begingroup$ Thank you Leonid, i was waiting for your answer :-) $\endgroup$
    – magma
    Commented Jan 20, 2012 at 14:46
  • 2
    $\begingroup$ @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. $\endgroup$ Commented Jan 20, 2012 at 15:43
  • 2
    $\begingroup$ @Eli see my answer for a working example of opts:OptionsPattern[] $\endgroup$
    – Mr.Wizard
    Commented Jan 20, 2012 at 16:54
  • 2
    $\begingroup$ @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. $\endgroup$ Commented Nov 12, 2019 at 19:14
48
$\begingroup$

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:

$\endgroup$
7
  • $\begingroup$ Oh man this answer helped me through hours of pain. Thanks! $\endgroup$
    – Gabriel
    Commented Oct 7, 2012 at 19:16
  • 1
    $\begingroup$ @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. $\endgroup$
    – Mr.Wizard
    Commented Oct 7, 2012 at 19:45
  • 1
    $\begingroup$ @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. :-) $\endgroup$
    – Mr.Wizard
    Commented May 21, 2015 at 18:51
  • 1
    $\begingroup$ @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[] $\endgroup$
    – Ali Hashmi
    Commented Feb 4, 2017 at 17:08
  • 1
    $\begingroup$ @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. $\endgroup$
    – Mr.Wizard
    Commented Feb 4, 2017 at 17:21
19
$\begingroup$

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]
$\endgroup$
5
  • 1
    $\begingroup$ I was aware of some of those but I never bothered to find them all. Good reference. $\endgroup$
    – Mr.Wizard
    Commented Apr 13, 2012 at 16:52
  • 4
    $\begingroup$ @Mr.Wizard grep General::opt Messages.m. :) $\endgroup$
    – rcollyer
    Commented Apr 13, 2012 at 16:59
  • $\begingroup$ 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? $\endgroup$
    – matheorem
    Commented Sep 29, 2017 at 14:25
  • $\begingroup$ @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. $\endgroup$
    – rcollyer
    Commented Sep 29, 2017 at 15:03
  • $\begingroup$ @rcollyer Thank you so much, very clear explanation : ) $\endgroup$
    – matheorem
    Commented Sep 29, 2017 at 15:23
16
$\begingroup$

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.

$\endgroup$
2
  • $\begingroup$ Thanks @rm -rf that looks much better. $\endgroup$
    – Stefan
    Commented Dec 19, 2012 at 14:25
  • $\begingroup$ 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. $\endgroup$
    – xyz
    Commented Jun 28, 2015 at 7:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.