Is it possible to separately call the default value of a variable which isn't at the end of the sequence?
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: ifNull
matches,param
in the RHS is effectively replaced withSequence[]
.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 viaSequence[]
.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
A simple solution
After writing a very long answer with one related method and four workarounds it suddenly hit me:
We need the behavior of a Symbol with the OneIdentity
attribute!
Observe:
Attributes[foo] = {OneIdentity};
MatchQ[Null, foo[Null, i_: 1]]
True
Times
is such a function, therefore we may use:
f[(Null i_: 1) | i_, (Null j_: 2) | j_, (Null k_: 3) | k_] := {i, j, k}
f[, ,]
f[, "b",]
f["a", "b",]
f["a", , "c"]
{1, 2, 3} {1, "b", 3} {"a", "b", 3} {"a", 2, "c"}
Success!
An edge case
Well, there is an edge case that must be addressed: when an argument is of the form Times[Null, . . .]
and one actually wants that passed to the RHS:
f[Null * "x", "b", "c"]
{"x", "b", "c"} (* failure *)
This can be addressed by using a localized Symbol with the OneIdentity
attribute:
ClearAll[f]
Module[{Star},
Attributes[Star] = {OneIdentity};
f[(Null⋆i_: 1) | i_, (Null⋆j_: 2) | j_, (Null⋆k_: 3) | k_] := {i, j, k}]
]
f[Null * "x", ,]
{"x" Null, 2, 3}
Meta-programming automation
Although I think this pattern is simple enough to use directly (at last!), we can also automate its creation if desired. This function uses a very general replacement on any Optional
pattern; more specificity may be desired such as targeting only the left-hand-side or looking no deeper than the first level; these are omitted for clarity but can be added if requested.
SetAttributes[makeFluidDefaults, HoldFirst]
makeFluidDefaults[x_Set | x_SetDelayed] :=
Module[{oi},
Attributes[oi] = {OneIdentity};
Unevaluated[x] /. op_Optional :> oi[Null, op] | op
]
Now:
ClearAll[f]
makeFluidDefaults[
f[i_: 1, j_: 2, k_: 3] := {i, j, k}
]
f[, ,]
f[, "b",]
f["a", "b",]
f["a", , "c"]
f[Null*"x", "b", "c"]
{1, 2, 3} {1, "b", 3} {"a", "b", 3} {"a", 2, "c"} {"x" Null, "b", "c"}
Here's one way to get the desired behavior, although it is not done with a single pattern:
Clear[f];
f[Null, v_, w_] := f[1, v, w];
f[u_, Null, w_] := f[u, 2, w];
f[u_, v_, Null] := f[u, v, 3];
f[u_, v_, w_] := u^2 + v^3 + w^4;
f[, v, w]
f[u, , w]
f[u, v,]
f[u, ,]
f[, v,]
f[, , w]
f[, ,]
1 + v^3 + w^4 8 + u^2 + w^4 81 + u^2 + v^3 89 + u^2 82 + v^3 9 + w^4 90