Mathematica: Checking your function for wrong options

7 minute read

Mathematica has a unique way of reporting wrong options. That is if you call a built-in function with an option that does not belong to this function, you see an error message of the following form and Mathematica returns your expression unevaluated:

Error message

We have to solve these problems:

  1. How can we identify wrong options
  2. How can we create a message that contains our original call
  3. How can we return unevaluated

1. Checking for wrong options

When we have a list of options, we can employ FilterRules to extract valid ones. Remember that FilterRules works on rules. Therefore, it is not restricted to Options and can be used in other situations as well. Consider the following simple example with a not yet defined function f

Options[f] = {optA -> Automatic, optB -> 1};
FilterRules[{optA -> MyValue, wrongOpt -> OtherValue}, Options[f]]

(* {optA -> MyValue} *)

The second argument of FilterRules is a pattern and, therefore, we can easily turn the selection around by using Except

FilterRules[{optA -> MyValue, wrongOpt -> OtherValue}, Except[Options[f]]]

(* {wrongOpt -> OtherValue} *)

You surely see that this is already the solution to this problem. If there is no invalid option in the list, the output will be the empty list and the user has not given any wrong option.

2. Creating a message that contains the wrong option and the original call

First, let us attach the error message to the symbol f that we will need for this. The two backtick pairs are the placeholder where we will inject the values later. As Michael has pointed out, this is not strictly necessary. The message General::optx already exists and if you don’t define it for your function, calling Message[f::optx] will define the messages for your symbol f and do the right thing.

f::optx = "Unknown option `` in ``."

Next, we will define a checkOpts function that tests the options and prints the message if there were any invalid options in the call. As we saw, we need both the options that were provided by the user and the original options of the function Since we also need the original call to print it, checkOpts will get your complete call as its argument and will extract all required things by itself.

Since your original call would be evaluated when we do checkOpts[f[args, opts]], we need to give checkOpts the attribute HoldFirst. This is one property of how Mathematica evaluates code: Arguments to a function are evaluated before the function is called. Therefore, we need to suppress this.

SetAttributes[checkOpts, HoldFirst];
checkOpts[call : func_[args__, opts : OptionsPattern[]]] := With[
  {
    bad = FilterRules[{opts}, Except[Options[func]]]
  },
  If[Length[bad] > 0,
   Message[func::optx, First[bad], HoldForm[call]];
   False, True
  ] 
]

The body of the function does nothing magical. It extracts that bad rules as discussed above and if there are any bad rules, it prints the message and returns False. Otherwise, it will return True. The interesting part is that we assign the complete function call with call : and that we use a generic func_ that can be any function, not only our f we are currently working on.

We can test this function even without having f properly defined yet

checkOpts[f[x, y, z, {optA -> 3}]]
(* True *)

checkOpts[f[x, y, z, {optA -> 3, wrongOpt -> Automatic}]]
(*
    During evaluation of In[14]:= f::optx: Unknown option wrongOpt->Automatic
      in f[x,y,z,{optA->3,wrongOpt->Automatic}].
    False
*)

3. How can use this in a definition, and we return unevaluated?

To use this, we test the whole call pattern of your function. That means we include a PatternTest like this

f[x_, OptionsPattern[]]?checkOpts := x^2 + OptionValue[optB]

Let’s test it. The default option value for optB is 1:

f[10]
(* 101 *)

Providing a custom option value for it

f[x, optB -> 10]
(* 10 + x^2 *)

and finally, giving something invalid

Error message

I promised that checkOpts is generic enough to use it in any other function as well. Note that now, I don’t define the message for combine and let Mathematica figure it out. Let us try this

Options[combine] := { function -> Plus };
combine[x_, y_, OptionsPattern[]]?checkOpts := OptionValue[function][x, y];

combine[20, 30]
(* 50 *)

combine[20, 30, function -> Times]
(* 600 *)

combine[20, 30, Frame -> True]
(*
    During evaluation of In[34]:= combine::optx: Unknown option 
        Frame->True in combine[20,30,Frame->True].
    Out[34]= combine[20, 30, Frame -> True]
*)