Prioritizing patterns
If you only want the default values for the leading parameters (or some other order you choose), and not an arbitrary parameter at the time of the function call, you can prioritize the patterns as described here.
f[
Shortest[u_: 1, 3],
Shortest[v_: 2, 2],
Shortest[w_: 3, 1]
] := u^2 + v^3 + w^4
f[v, w]
1 + v^3 + w^4
Workaround No.1
I am still seeking a pattern-based solution to the arbitrary parameter problem, but until and unless I find it here is a work-around you may consider:
g[Null | u_, Null | v_, Null | w_] :=
With[{uu = # &[u, 1], vv = # &[v, 2], ww = # &[w, 3]},
{{"u", uu}, {"v", vv}, {"w", ww}}
]
g[, v, w]
g[u, , w]
g[u, v,]
{{u,1}, {v,v}, {w,w}}
{{u,u}, {v,2}, {w,w}}
{{u,u}, {v,v}, {w,3}}
How this works:
The pattern Null | param_
is used for each parameter: if Null
matches, param
in the RHS is effectively replaced with Sequence[]
.
The function # &
will return the first argument be there one or more than one; by specifying the optional/default values as the second paramter this will be returned iff the first is removed via Sequence[]
.
With
may not always be necessary but it is used for robustness.
Workaround No.2
I think this is a bit cleaner in principle than No.1 though neither is fully satisfying.
h[Null | aa_, Null | bb_, Null | cc_] :=
{{aa}, {bb}, {cc}} /. {{a_: 1}, {b_: 2}, {c_: 3}} :> {a, b, c}
h[5, ,]
h[, 7,]
h[, , 13]
{5, 2, 3}
{1, 7, 3}
{1, 2, 13}
Workaround No.3
I don't think this is as clean as No.2 but it should be easier to extend.
For functions without a hold attribute you could use this:
ClearAll[f]
lhs : f[___, Null, ___] :=
With[{def = {"1", "2", "3", "4", "5", "6", "7", "8", "9"}},
MapIndexed[# /. Null -> def[[First @ #2]] &, Unevaluated[lhs]]
]
f[1, , 3, 4, , 6, , , 9] // InputForm
f[1, "2", 3, 4, "5", 6, "7", "8", 9]
The list def
gives defaults by position for each parameter. Here I chose numeric strings to make the positions evident while still illustrating the substitution.
If your function does have Hold attributes you will need a slightly more complicated replacement. I will make use of RuleCondition
(1):
ClearAll[f]
SetAttributes[f, HoldAll]
lhs : f[___, Null, ___] :=
Module[{i = 1, def = ToString ~Array~ 9},
Replace[
Unevaluated[lhs],
{Null :> RuleCondition @ def[[i++]], x_ /; (i++; True) :> x},
{1}
]
]
f[1, , 3, 2 + 2, , 6, , , 3^2] // InputForm
f[1, "2", 3, 2 + 2, "5", 6, "7", "8", 3^2]
Notice that 4
and 9
have been replaced with 2 + 2
and 3^2
and that these correctly remain unevaluated after the substitution.
Workaround No.4
Michael E2 showed what is perhaps the most declarative method. I don't think by itself it is as easily extensible, but then again there must be a limit to how many arguments a user is going to count anyway. Nevertheless one could automate that method with meta-programming, so let's do it.
makeDefaults[fname_Symbol, defaults_List] :=
fname @@@ {Pattern[#, _] & /@ #, #} & @ Table[Unique["$"], {Length @ defaults}] //
Do[
SetDelayed @@ ReplacePart[#, {{1, n} -> Null, {2, n} -> defaults[[n]]}],
{n, Length @ defaults}
] &
Now:
makeDefaults[fn, {"one", "two", "three"}]
fn[u_, v_, w_] := u^2 + v^3 + w^4
?? fn
Global`fn
fn[Null, $2_, $3_] := fn["one", $2, $3]
fn[$1_, Null, $3_] := fn[$1, "two", $3]
fn[$1_, $2_, Null] := fn[$1, $2, "three"]
fn[u_, v_, w_] := u^2 + v^3 + w^4
f[ , v, w]
is identical tof[Null, v, w]
, so the valueNull
is being passed foru
. The problem is how to program a certain behavior when a particular value,Null
, is passed as a parameter. $\endgroup$