39
$\begingroup$

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

Instead I get

{{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?

$\endgroup$
  • 1
    $\begingroup$ Are you an R user? I love this feature in R ... never seen it in any other language. $\endgroup$ – Gabriel Dec 4 '12 at 20:41
25
$\begingroup$

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

$\endgroup$
26
$\begingroup$

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}}  *)
$\endgroup$
  • 2
    $\begingroup$ @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 :) $\endgroup$ – Leonid Shifrin Dec 4 '12 at 21:20
  • 1
    $\begingroup$ You can dispense with the Module and simply write: g[x_, y_: 0, z_: \[FormalY]] := Block[{\[FormalY] = y}, {x, y, z}]. It makes for cleaner looking code in the FE :) $\endgroup$ – rm -rf Dec 4 '12 at 21:20
  • 2
    $\begingroup$ @rm-rf: You guys are scaring me. $\endgroup$ – orome Dec 4 '12 at 21:32
  • 1
    $\begingroup$ @rm-rf Besides, what makes you think that \[FormalY] has not been uprotected and assigned a value? :) $\endgroup$ – Leonid Shifrin Dec 4 '12 at 21:33
  • 1
    $\begingroup$ @LeonidShifrin If we're going down that road, what makes you think Module has not been unprotected and redefined? :D Nevertheless, point taken. (I do agree with the readability in any editor part — a fact I began to appreciate only after using the WB) $\endgroup$ – rm -rf Dec 4 '12 at 21:44
11
$\begingroup$

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}
$\endgroup$
  • $\begingroup$ super cute :-) +1 $\endgroup$ – Gabriel Dec 4 '12 at 22:46
  • $\begingroup$ 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. $\endgroup$ – Leonid Shifrin Dec 4 '12 at 23:03
  • $\begingroup$ @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. $\endgroup$ – István Zachar Dec 5 '12 at 20:36
10
$\begingroup$

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.

$\endgroup$
6
$\begingroup$

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
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.