Take the 2-minute tour ×
Mathematica Stack Exchange is a question and answer site for users of Mathematica. It's 100% free, no registration required.

After reading this post I recalled an old question I once met. We know that we can set default values for variables of a function:

f[u_: 1, v_: 2, w_: 3] := u^2 + v^3 + w^4

We can use the default values like this:

f[]

90

like this:

f[u]

89 + u^2

and like this:

f[u, v]

81 + u^2 + v^3

However, what if I want to call the default value of a variable not at end separately? Something like

f[, v, w]

only gives

Null^2 + v^3 + w^4

while what I want to see is

1 + v^3 + w^4

Of course it's easy to circumvent, but I still wonder if there's a more straight forward solution.

share|improve this question
    
@Nasser Well isn't that what options are? –  sebhofer Jul 26 '13 at 11:12
    
Just to clarify "value...not at end separately": The input f[ , v, w] is identical to f[Null, v, w], so the value Null is being passed for u. The problem is how to program a certain behavior when a particular value, Null, is passed as a parameter. –  Michael E2 Jul 26 '13 at 12:44
    
@Nasser Sure they could, but I don't think the solution would fit the OP's expectation. –  sebhofer Jul 26 '13 at 14:15
    
Thanks for the Accept. I added two more methods that should be easily extensible; I hope you like them. :-) –  Mr.Wizard Jul 28 '13 at 8:58
    
xzczd, I think you'll want to see the new answer I just posted. My brain finally warmed up it seems. :^) –  Mr.Wizard Jul 28 '13 at 16:14

4 Answers 4

up vote 7 down vote accepted

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 of 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"}
share|improve this answer
    
Brilliant stuff, and quite interesting behavior on it's own. So what's happening is that Replace[Null, foo[Null, i_: 1] :> i] Matches the pattern, but still doesn't match the pattern, and therefore returns the default. This is kind of the behavior I would have expected from Alternatives, and I actually thought the difference in behavior was the inability of Mathematica to do this kind of simultaneous match/no match. Turns out that wasn't so. –  jVincent Jul 28 '13 at 17:24
    
Excellent! After you left your comment, I tried to figure out something similar -- different approach, not nearly so elegant, and not yet bug free. –  Michael E2 Jul 28 '13 at 20:06
1  
@jVincent I think the case with Alternatives is that each element is considered sequentially; as soon as Null in Null | i_:1 matches the rest is ignored; this is possibly an important optimization even though it's a sad tradeoff in this context. With this pattern Null i_:1 it is a single expression; because of OneIdentity, Null is transparently converted to (or at least treated like) Times[Null] which then matches Times[Null, i_:1] with i_:1 playing the usual role it would in any other expression. –  Mr.Wizard Jul 28 '13 at 21:58
    
@Michael out of curiosity: what was your avenue of attack? –  Mr.Wizard Jul 28 '13 at 21:59
    
It was to automate my "declarative method", with a somewhat different (and needlessly complicated?) idea than yours/jVincent's. It was similar in that it was called in the same, but instead of Optional, I was going to use my own head default. I'm not particularly good at this sort of thing, which is why I was trying to learn and maybe why I was tempted to complicate it. I can post it if you like. It works with basic patterns (I think), but I got stuck extending it to things like PatternTest. –  Michael E2 Jul 28 '13 at 22:16

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
share|improve this answer
    
That's interesting :) but what I expect is just a f which works for f[, v, w], f[u, , w], f[ , , w] and so on simultaneously… –  xzczd Jul 26 '13 at 9:19
    
@xzczd Alright. That's a harder problem. I cannot think of a way to do that with patterns alone. Should I give my own work-around or since you said "it's easy to circumvent" is that not of interest? –  Mr.Wizard Jul 26 '13 at 9:25
    
Just add it, according to my experience, your work-around will probably show me something new :) –  xzczd Jul 26 '13 at 9:31
3  
I've always felt that this sort of thing should have been implemented by allowing Optional to take default values when not matched in Alternatives. It just seems like a more intuitive process, that whenever an optional isn't matched, it returns it's default. If that had been the case, this particular design pattern would be a trivial pattern match to Null|v_:1. –  jVincent Jul 26 '13 at 10:16
1  
@jVincent That certainly would make this particular case easier! It might however complicate other uses? I'm afraid Alternatives in this application doesn't get a lot of attention; I wouldn't be surprised if these things were never seriously considered before the implementation was chosen. –  Mr.Wizard Jul 26 '13 at 10:20

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
share|improve this answer
    
It's worth to mention that the order of the 4 definitions is important :) –  xzczd Jul 26 '13 at 13:00
1  
@xzczd The ordering of definitions is usually important, but I find it doesn't matter in this case if I randomly order them. (The first three are specific and order makes no difference with them; the fourth is general, so Mathematica reorders it last no matter what.) Is there an order you find does not work? –  Michael E2 Jul 26 '13 at 13:09
    
Ah… I should have checked it more carefully, you are right, the order doesn't care in this case. –  xzczd Jul 26 '13 at 13:20
    
This is a nice, declarative method that I should have included in the first place (you've got my vote). I added an automated version of this as "No.4" to my answer; take a look if you're interested. –  Mr.Wizard Jul 28 '13 at 9:00

Not sure if this is something in the direction of the named parameters as mentioned by Nasser, however a possible alternative could be:

gg[u[uu_:1],v[vv_:2],z[zz_:3]]:=Print[Column[{Row[{"u=", uu}],Row[{"v=", vv}],Row[{"z =",zz}]}]]

gg[u[1], v[2], z[3]]
u = 1 
v = 2 
z = 3

gg[u[], v[2], z[3]]
u = 1 
v = 2 
z = 3

gg[u[], v[5], z[]]
u = 1 
v = 5 
z = 3

Indeed, without naming the parameters or as alternative using Null (or any symbol like a placeholder), I guess there is no other way to say to the RHS of the definition, which variables is missing.

share|improve this answer
    
Forgive me, but this seems to be the worst of all worlds as it were. You get neither the simplicity of using Null nor the flexibility of using Options. If one is using named parameters I think in all cases it would be better to use Options directly, e.g. gg["u" -> 1, "v" -> 2]. –  Mr.Wizard Jul 28 '13 at 9:03

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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