6

Suppose I have a function with optional named arguments but I insist on referring to the arguments by their unadorned names.

Consider this function that adds its two named arguments, a and b:

Options[f] = {a->0, b->0};  (* The default values. *)
f[OptionsPattern[]] := 
  OptionValue[a] + OptionValue[b]

How can I write a version of that function where that last line is replaced with simply a+b? (Imagine that that a+b is a whole slew of code.)

The answers to the following question show how to abbreviate OptionValue (easier said than done) but not how to get rid of it altogether: Optional named arguments in Mathematica

Philosophical Addendum: It seems like if Mathematica is going to have this magic with OptionsPattern and OptionValue it might as well go all the way and have a language construct for doing named arguments properly where you can just refer to them by, you know, their names. Like every other language with named arguments does. (And in the meantime, I'm curious what workarounds are possible...)

Community
  • 1
  • 1
dreeves
  • 26,430
  • 45
  • 154
  • 229

3 Answers3

7

Why not just use something like:

Options[f] = {a->0, b->0};
f[args___] := (a+b) /. Flatten[{args, Options[f]}]

For more complicated code I'd probably use something like:

Options[f] = {a->0, b->0};
f[OptionsPattern[]] := Block[{a,b}, {a,b} = OptionValue[{a,b}]; a+b]

and use a single call to OptionValue to get all the values at once. (Main reason is that this cuts down on messages if there are unknown options present.)

Update, to programmatically generate the variables from the options list:

Options[f] = {a -> 0, b -> 0};
f[OptionsPattern[]] := 
  With[{names = Options[f][[All, 1]]}, 
    Block[names, names = OptionValue[names]; a + b]]
Brett Champion
  • 8,497
  • 1
  • 27
  • 44
  • Oh, nice! One quasiconstraint I failed to mention: In practice I have a ton of options and would like to not have to list them all explicitly twice. (Your second solution -- which I probably would need -- lists them three extra times.) – dreeves Jan 13 '11 at 22:15
  • You can generate the list programmatically. I'll edit my answer in a moment. – Brett Champion Jan 13 '11 at 22:50
  • 1
    This is a clean solution to the problem. However, it breaks if one tries to do a partial implementation. As an example, `Options[f] = {a -> 0, b -> 0}; f[OptionsPattern[]] := Block[{a, b}, a = OptionValue[a]; a + OptionValue[b]]; f[a -> 1]`, which returns the error message `OptionValue::rep: "1->1 is not a valid replacement rule."` – E.P. Sep 21 '15 at 17:36
4

Here is the final version of my answer, containing the contributions from the answer by Brett Champion.

ClearAll[def];
SetAttributes[def, HoldAll];
def[lhs : f_[args___] :> rhs_] /; !FreeQ[Unevaluated[lhs], OptionsPattern] :=
   With[{optionNames = Options[f][[All, 1]]},
     lhs := Block[optionNames, optionNames = OptionValue[optionNames]; rhs]];
def[lhs : f_[args___] :> rhs_] := lhs := rhs;

The reason why the definition is given as a delayed rule in the argument is that this way we can benefit from the syntax highlighting. Block trick is used because it fits the problem: it does not interfere with possible nested lexical scoping constructs inside your function, and therefore there is no danger of inadvertent variable capture. We check for presence of OptionsPattern since this code wil not be correct for definitions without it, and we want def to also work in that case. Example of use:

Clear[f, a, b, c, d];
Options[f] = {a -> c, b -> d};
(*The default values.*)
def[f[n_, OptionsPattern[]] :> (a + b)^n]

You can look now at the definition:

Global`f
f[n$_,OptionsPattern[]]:=Block[{a,b},{a,b}=OptionValue[{a,b}];(a+b)^n$]

f[n_,m_]:=m+n

Options[f]={a->c,b->d}

We can test it now:

In[10]:= f[2]
Out[10]= (c+d)^2

In[11]:= f[2,a->e,b->q]
Out[11]= (e+q)^2

The modifications are done at "compile - time" and are pretty transparent. While this solution saves some typing w.r.t. Brett's, it determines the set of option names at "compile-time", while Brett's - at "run-time". Therefore, it is a bit more fragile than Brett's: if you add some new option to the function after it has been defined with def, you must Clear it and rerun def. In practice, however, it is customary to start with ClearAll and put all definitions in one piece (cell), so this does not seem to be a real problem. Also, it can not work with string option names, but your original concept also assumes they are Symbols. Also, they should not have global values, at least not at the time when def executes.

Leonid Shifrin
  • 22,449
  • 4
  • 68
  • 100
  • This is impressive. You must be a Lisp hacker. Thanks so much for the help! I think I'm ok with the restriction that the named parameters not have global values since that's a restriction in the normal way of doing named arguments as well. – dreeves Jan 13 '11 at 22:33
  • @dreeves: Lisp/Scheme/Clojure is on my wish list. No time right now, alas. Mathematica has been my first language which I took seriosly. I actually was also annoyed for a long time by the need of wrapping OptionValue all the time, but your question prompted me finally to do something about it. I will likely start using this myself. – Leonid Shifrin Jan 13 '11 at 22:52
  • @Leonid: Very cool. Though I'm now wondering, in light of Brett's update to his answer, if it's worth using this hairy macro. – dreeves Jan 13 '11 at 23:17
  • @dreeves: I just updated my answer using ideas from Brett's solution, now it is much simpler than before – Leonid Shifrin Jan 13 '11 at 23:19
  • Ah, wonderful! Want to clean it up and get rid of the old version and I'll mark this the accepted answer? Thanks again! – dreeves Jan 13 '11 at 23:50
  • @dreeves: Cleaned it up. Is there a way to share the credit? Brett's influence on the final version is at least as big as mine. – Leonid Shifrin Jan 14 '11 at 00:24
  • I could have you stochastically share the credit by randomizing. :) But no, like you said elsewhere, the real point is come up with a definitive answer for future users who come to this page. Stealing from other answers to do that has been encouraged by the founders from the beginning. – dreeves Jan 14 '11 at 05:14
0

Here's a kind of horrific solution:

Options[f] = {a->0, b->0};
f[OptionsPattern[]] := Module[{vars, tmp, ret},
  vars = Options[f][[All,1]];
  tmp = cat[vars];
  each[{var_, val_}, Transpose[{vars, OptionValue[Automatic,#]& /@ vars}],
    var = val];
  ret = 
    a + b;  (* finally! *)
  eval["ClearAll[", StringTake[tmp, {2,-2}], "]"];
  ret]

It uses the following convenience functions:

cat = StringJoin@@(ToString/@{##})&;        (* Like sprintf/strout in C/C++.  *)
eval = ToExpression[cat[##]]&;              (* Like eval in every other lang. *)
SetAttributes[each, HoldAll];               (* each[pattern, list, body]      *)
each[pat_, lst_, bod_] := ReleaseHold[      (*  converts pattern to body for  *)
  Hold[Cases[Evaluate@lst, pat:>bod];]];    (*   each element of list.        *)

Note that this doesn't work if a or b has a global value when the function is called. But that was always the case for named arguments in Mathematica anyway.

dreeves
  • 26,430
  • 45
  • 154
  • 229
  • while we are at it, let me point out that your "each" function leaks evaluation. Try this: {a, b, c} = {1, 2, 3}; each[{var_, val_}, {{a, 4}, {b, 5}, {c, 6}}, var = val]. Besides,the idiom ReleaseHold[Hold[f[..,Evaluate[..],..] does not work since Evaluate is too deep inside Hold. Here is a version which seems better: SetAttributes[each, HoldAll]; each[pat_, lst_, bod_] := Cases[Hold[lst], pat :> bod, {2}]; – Leonid Shifrin Jan 13 '11 at 19:58
  • actually, even better version: SetAttributes[each, HoldAll]; each[pat_, lst_, bod_] := Cases[Unevaluated[lst], pat :> bod]; Or, may be, I am just missing the point (the purpose of your function)? – Leonid Shifrin Jan 13 '11 at 20:04
  • @Leonid: Thanks! For the "each" function, see here: http://stackoverflow.com/questions/160216/foreach-loop-in-mathematica – dreeves Jan 13 '11 at 22:20
  • Daniel, thanks for the link. Unfortunately, this seems to be a common misunderstanding. I actually can not think of any case where the construct ReleaseHold[Hold[something]] could be meaningful, as it stands. This could be meaningful: ReleaseHold[Hold[something]/.{some rules}] (I use this a lot), or when you inject something with With. Hold is most useful to keep expression held in between several evaluations, not a single one. I discussed it here, as well as some related stuff: http://groups.google.com/group/comp.soft-sys.math.mathematica/browse_thread/thread/bfd67e9122b1fdec – Leonid Shifrin Jan 13 '11 at 22:47