Functions with Options

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

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:

  • SyntaxInformation: take allowed options from multiple symbols – possible?

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]