15

What's the best/canonical way to define a function with optional named arguments? To make it concrete, let's create a function foo with named arguments a, b, and c, which default to 1, 2, and 3, respectively. For comparison, here's a version of foo with positional arguments:

foo[a_:1, b_:2, c_:3] := bar[a,b,c]

Here is sample input and output for the named-arguments version of foo:

foo[]                  --> bar[1,2,3]
foo[b->7]              --> bar[1,7,3]
foo[a->6, b->7, c->8]  --> bar[6,7,8]

It should of course also be easy to have positional arguments before the named arguments.

dreeves
  • 26,430
  • 45
  • 154
  • 229
  • See also: http://stackoverflow.com/questions/4682742/optional-named-arguments-without-wrapping-them-all-in-optionvalue – dreeves Jan 15 '11 at 19:40

3 Answers3

14

I found the standard way to do it in the Mathematica documentation: http://reference.wolfram.com/mathematica/tutorial/SettingUpFunctionsWithOptionalArguments.html

Options[foo] = {a->1, b->2, c->3};  (* defaults *)
foo[OptionsPattern[]] := bar[OptionValue@a, OptionValue@b, OptionValue@c]

Typing "OptionValue" every time is a little cumbersome. For some reason you can't just make a global abbreviation like ov = OptionValue but you can do this:

foo[OptionsPattern[]] := Module[{ov},
  ov[x___] := OptionValue[x];
  bar[ov@a, ov@b, ov@c]]

Or this:

With[{ov = OptionValue},
  foo[OptionsPattern[]] := bar[ov@a, ov@b, ov@c]
]

Or this:

$PreRead = ReplaceAll[#, "ov" -> "OptionValue"] &;

foo[OptionsPattern[]] := bar[ov@a, ov@b, ov@c]
Mr.Wizard
  • 24,179
  • 5
  • 44
  • 125
dreeves
  • 26,430
  • 45
  • 154
  • 229
  • About cumbersome typing of OptionValue : In this case you could say `In[32]:= OptionValue /@ bar[a, b, c] Out[32]= bar[OptionValue[a], OptionValue[b], OptionValue[c]]` – Sjoerd C. de Vries Mar 17 '11 at 07:28
  • 1
    @dreeves There is a more concise form of the last code block using `With` rather than `Module`. May I edit your answer to append this? – Mr.Wizard Mar 17 '11 at 08:07
  • @Sjoerd funny, your comment was not there when I loaded this page. I guess you're reading these old posts too. – Mr.Wizard Mar 17 '11 at 08:08
  • @Mr.Wizard Yep, triggered by dreeves' chain of questions. – Sjoerd C. de Vries Mar 17 '11 at 08:50
  • @Mr.Wizard Please do! (I don't think you need to ask in general; reverting is easy if the original author doesn't like the edit.) Oh, and don't append, just change it if it's better. – dreeves Mar 18 '11 at 08:09
  • @dreeves I know the site allows that, but it still seems polite to ask. BTW, I don't know if it's better, just a different style. – Mr.Wizard Mar 18 '11 at 08:44
  • @Mr.Wizard Are you sure that PreRead trick is safe? What if "ov" is just part of another string or redefined in a Block or something? Also, did you test the With version? There's something weird about OptionValue; confer my "For some reason you can't...". – dreeves Mar 24 '11 at 00:29
  • I did test these, but recall that I am using Mma 7. The `With` version works because replacement is done before evaluation. The `$PreRead` version appears safe because it operates at the token level. Try evaluating: `{7 "ov", 2 + ov, "stove", rover}` – Mr.Wizard Mar 24 '11 at 08:01
6

Yes, OptionValue can be a bit tricky because is relies on a piece of magic so that

OptionValue[name] is equivalent to OptionValue[f,name], where f is the head of the left-hand side of the transformation rule in which OptionValue[name] appears.

Throwing in an explicit Automatic usually does the trick, so in your case I would say that the solution is:

Options[foo] = {a -> 1, b -> 2, c -> 3};
foo[OptionsPattern[]] := 
  bar @@ (OptionValue[Automatic, #] &) /@ First /@ Options[foo] 

By the way, options used to be done by matching to opts:___?OptionQ, and then finding option values manually as {a,b,c}/.Flatten[{opts}]. The pattern check OptionQ is still around (although not documented), but the OptionValue approach has the advantage that you get warnings for non-existing options (e.g. foo[d->3]). This would also be the case for your second response, but not for the one you have accepted.

Janus
  • 5,421
  • 2
  • 26
  • 37
1

I'll throw this possible solution into the mix:

foo[opts___Rule] := Module[{f},
  f@a = 1; (* defaults... *)
  f@b = 2;
  f@c = 3;
  each[a_->v_, {opts}, f@a = v];

  Return[bar[f@a, f@b, f@c]]
]

I like it for its terseness but I don't think it's the standard way. Any gotchas with doing it that way?

PS, it uses the following handy utility function:

SetAttributes[each, HoldAll];                (* each[pattern, list, body]     *)
each[pat_, lst_, bod_] :=                    (*  converts pattern to body for *)
  Scan[Replace[#, pat:>bod]&, Evaluate@lst]  (*   each element of list.       *)
dreeves
  • 26,430
  • 45
  • 154
  • 229