# Can I make a default for an optional argument the value of another argument?

I'd like to define a function with several optional arguments, some of which default to the value supplied for other arguments. For example, I'd like to be able to write something like

f[x_, y_: 0, z_: y] := {x, y, z}


and have

{f[1, 2], f[1]}


produce

{{1, 2, 2}, {1, 0, 0}}


{{1, 2, y}, {1, 0, y}}


Can I make a default for an optional argument the value of another argument? If not, what's the best approach for accomplishing this?

-
Are you an R user? I love this feature in R ... never seen it in any other language. – Gabriel Dec 4 '12 at 20:41

You can't easily do this with optional arguments (but see Leonid's answer for a work around), but you can use the fact that you can have multiple definitions for a given function:

f[x_, y_:0] := {x, y, y}
f[x_, y_, z_] := {x, y, z}


will do what you want.

For further use of this style you could also do this as:

f[x_] := {x, 0, 0}
f[x_, y_] := {x, y, y}
f[x_, y_, z_] := {x, y, z}


which makes the "pattern" of your function even more explicit

-

Yes you can, although this is not completely trivial:

Module[{yy},
f[x_, y_: 0, z_: yy] := Block[{yy = y}, {x, y, z}]
]


What is happening here is that I set the default to a local variable, which I then dynamically set (via Block) to a second argument. So,

{f[1,2],f[1]}

(*  {{1,2,2},{1,0,0}}  *)

-
ohhhhh didn't realize this works. Any thoughts on why Mathematica color codes the yy in the block red? Seems to think it is not used. Man this is soooo sweet you get the R behavior as well, you can make the default arguments expressions like 1/yy etc. Thanks for this! – Gabriel Dec 4 '12 at 21:01
@Gabriel Well, a number of constructive uses of nested scoping constructs formally create variable collisions. Those who use that stuff know exactly how they are resolved, but it makes sense for the highlighter to generally warn about those. In this particular case, the way it works is that Block does localize the special variable created by Module, not the top-level yy, so things are fine. – Leonid Shifrin Dec 4 '12 at 21:06
@Gabriel Sure, no problem :) – Leonid Shifrin Dec 4 '12 at 21:07
man thought I had this question in the bag ... then the giants come round ;-) – Gabriel Dec 4 '12 at 21:17
@Gabriel One must be careful with saying "x is impossible in Mathematica", I think most of us got into this trap at least once. Never say never :) – Leonid Shifrin Dec 4 '12 at 21:20

A less elegant version than Gabriel's, and a less economic than Leonid's, using ReplaceAll:

f[x_, y_: 0, z_: Automatic] := {x, y, z /. Automatic -> y}

-
super cute :-) +1 – Gabriel Dec 4 '12 at 22:46
Very nice, +1. The reason I did not go this way is that you will have to hunt down all occurrences of z and replace them with z/.Automatic -> y, and if you forget some, there isn't any easy way to see that, so for functions with more complex / larger bodies this will be harder to maintain. But for a simple function, this is quite elegant. – Leonid Shifrin Dec 4 '12 at 23:03
@Leonid yes, you are right, but I had the vision of Options in front of my eyes :) Of course that involves a Module and a local variable to replace z everywhere, as you have pointed out. – István Zachar Dec 5 '12 at 20:36

Already late to the party, but here is another approach:

ClearAll[f]
f[x_, y_: Automatic] :=
If[y === Automatic, {x, x}, {x, y}]


Another Optional trick is the following:

ClearAll[f]
f[x : (y_) : 1] := {x, y}


Here the colon is used twice. Once as shorthand for Pattern and once as shorthand for Optional. This is not appropriate for you question. I just wanted to mention it.

Edit 1:

Since optional arguments are all about pattern matching, here a list of possible patterns and allowed syntax:

InputForm  | FullForm
-----------|---------
x          |  x
_*x        |  Times[Blank[], x]
(_.)*x     |  Times[Optional[Blank[]], x]
_          |  Blank[]
x*_        |  Times[x, Blank[]]
_x         |  Blank[x]
x . _      |  Dot[x, Blank[]]
_ . x      |  Dot[Blank[], x]
_.         |  Optional[Blank[]]
x*(_.)     |  Times[x, Optional[Blank[]]]
x_.        |  Optional[Pattern[x, Blank[]]]
_:x        |  Optional[Blank[], x]
x_         |  Pattern[x, Blank[]]
x:(_.)     |  Pattern[x, Optional[Blank[]]]
x /. _     |  ReplaceAll[x, Blank[]]
x /. _.    |  ReplaceAll[x, Optional[Blank[]]]
_ /. x     |  ReplaceAll[Blank[], x]
_. /. x    |  ReplaceAll[Optional[Blank[]], x]


Edit 2:

Another alternative is the following:

Default[f] = def;
f[x_, y_.] := Block[{def = x}, {x, y}]


Probably this is the best form of all I've listed here.

-

I think a good way to handle this is to pass the arguments back to the function. This is very similar to what Gabriel already proposed, but when the RHS is more complex it will be cleaner. Unlike Leonid's method it does not rely on Symbol evaluation and will therefore work inside held constructs.

Example:

f[x_, y_: 0]  := f[x, y, y]
f[x_, y_, z_] := Hold[x, y, z]

f[5]

f[5, 7]

Hold[5, 0, 0]

Hold[5, 7, 7]


If the actual RHS were longer you can see why one would not wish rewrite it for the optional case:

f[x_, y_, z_] :=
-x^3 - x^4 - x^3 y + 3 x^2 z + 3 x^3 z + 3 x^2 y z - 3 x z^2 - 3 x^2 z^2 -
3 x y z^2 + z^3 + x z^3 + y z^3

-