21

Is there a way to call a function where the "middle" arguments should use their default value while specifying values for the "right-most" arguments? For example, given this function:

f[a_String: "a", b_String: "b", c_String: "c"] :=  a<>b<>c

you can leave off the last argument, so f["x","y"] returns "xyc". I would like to call f in a way like f["x",,"z"] to use the default second argument and return xbz, but this syntax doesn't work. Is there a (non-contrived) way to do this in Mathematica?

Mike Pierce
  • 593
  • 5
  • 14

4 Answers4

17

Define function with Null inputs

Clear[f]

f[a_String: "a", Null, c_String: "c"] := a <> "b" <> c;

f[Null, b_String: "b", c_String: "c"] := "a" <> b <> c;

f[Null, Null, c_String: "c"] := "a" <> "b" <> c;

f[a_String: "a", b_String: "b", c_String: "c"] := a <> b <> c;

{f["x", "y", "z"], f["x", "y"], f["x", , "y"], f["x"], f["x",], 
 f[, "x", "y"], f[, , "x"], f[], f[, ]}

(*  {"xyz", "xyc", "xby", "xbc", "xbc", "axy", "abx", "abc", "abc"}  *)
Bob Hanlon
  • 157,611
  • 7
  • 77
  • 198
10

We can use BlankNullSequence to keep from listing all possibilities of missing arguments. We can use recursion f[a_]:=f[b] to keep from having to repeatedly write the function body.

f[a___, Null, b___] := 
  (* pair inputs with defaults *)
  Transpose[{
     {a, Null, b},
     {"defaultA", "defaultB", "defaultC", "defaultD"}
  }]//
  (* input or default? *)
  # /. {
    {Null, default_} :>  default,
    {input_, default_} :> input
  }& //
  (* call function with good arguments *)
  Apply[f]

f[a_String,b_String,c_String,d_String]:= a<>b<>c<>d

Actually, that may have problems if the inputs involve lists rather than just strings. This should be better.

f[a___, Null, b___] := 
  (* pair inputs with defaults *)
  Transpose[{
     {a, Null, b},
     {"defaultA", "defaultB", "defaultC", "defaultD"}
  }] //
  (* input or default? *)
  Map[If[First@#===Null,Last@#,First@#]&] //
  (* call function with good arguments *)
  Apply[f]

f[a_String,b_String,c_String,d_String]:= a<>b<>c<>d

When we pair the inputs with the defaults, we need to make sure there are enough inputs. We can use PadRight to indicate that optional arguments to the right have been omitted.

f[a___, Null, b___] := 
  (* list default values *)
  {"defaultA", "defaultB", "defaultC", "defaultD"} //
  (* pair inputs with defaults *)
  {PadRight[{a, Null, b}, Length@#, Null],#}& //
  Transpose //
  (* input or default? *)
  Map[If[First@#===Null,Last@#,First@#]&] //
  (* call function with good arguments *)
  Apply[f]

f[a_String,b_String,c_String,d_String]:= a<>b<>c<>d
Timothy Wofford
  • 3,803
  • 2
  • 19
  • 24
8

Here's yet another way that I just discovered:

ClearAll[f]
Default[f, 1] = "a"
Default[f, 2] = "b"
Default[f, 3] = "c"
f[x___, Null, y___] := f[x, Default[f, Length[{x}]+1], y]
f[a_., b_., c_.] := a <> b <> c

Here are some examples, tested with Mathematica 8.0:

{f[],f[Null],f[,],f[,,]}
(*
==> {abc,abc,abc,abc}
*)
{f["A"],f["A",],f["A",,]}
(*
==> {Abc,Abc,Abc}
*)
{f[,"B"],f[,"B",]}
(*
==> {aBc,aBc}
*)
f["A",,"C"]
(*
==> AbC
*)
f["A","B","C"]
(*
==> ABC
*)

Here's how it works:

  • If you don't provide an optional argument in your call, Mathematica supplies it from the corresponding Default assignment.
  • If you omit an argument in between, Null is passed, and you match the first definition, which explicitly replaces that Null by the corresponding Default value. If there's more than one Null value, the process repeats until all Null values are replaced.
celtschk
  • 19,133
  • 1
  • 51
  • 106
7

When you omit a parameter in the middle then Null is passed. You can check for this, replace the value with a default value, and continue with the execution.

f[x_: "a", y_: "b", z_: "c"] := Module[{}, {x, y, z}]

As you already know, this gives you the standard default behaviour. You can omit parameters from the right. The use of Module will become clear.

{f["i", "j"], f["i"], f[]}
(* {{"i", "j", "c"}, {"i", "b", "c"}, {"a", "b", "c"}} *)

We can add a sub-module that will check for the nulls and replace them.

ClearAll[f];

f[x_: "a", y_: "b", z_: "c"] :=
 Module[{xi, yi, zi},
  (* Default body *)
  Module[{args = {x, y, z}, defaults = {"a", "b", "c"}},
   MapIndexed[
    Function[{item, index}, 
     args[[First@index]] = 
      If[item === Null, defaults[[First@index]], item]], args, {1}];
   {xi, yi, zi} = args;
   ];
  (* Function body*)
  {xi, yi, zi}
  ]

There is a duplication of the defaults in the submodule. We can't assign a value to the function parameter symbols so we have to change to some inner symbols {xi, yi, zi}. However, it works as expected.

{f["i", "j"], f["i"], f[]}
(* {{"i", "j", "c"}, {"i", "b", "c"}, {"a", "b", "c"}} *)

f["i", , "k"]
(* {"i", "b", "k"} *)

This method should work for any combination of omitted parameters.

Hope this helps.

Edmund
  • 42,267
  • 3
  • 51
  • 143