21
$\begingroup$

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?

$\endgroup$
0

4 Answers 4

8
$\begingroup$

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.
$\endgroup$
17
$\begingroup$

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"}  *)
$\endgroup$
3
  • $\begingroup$ Yeah, this works, but not quite as clean/concise as I was hoping. This will become especially verbose if there are more arguments, or if the function body is longer. $\endgroup$ Sep 24, 2015 at 20:59
  • 3
    $\begingroup$ An alternative is to define something like f[a_, Null, c_] := f[a, "b", c]; that way you don't have to repeat the body of the function. $\endgroup$ Sep 24, 2015 at 21:59
  • $\begingroup$ @2012rcampion Beat me to it by seconds! $\endgroup$ Sep 24, 2015 at 22:00
10
$\begingroup$

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

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.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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