14

One may set a Default value for the arguments of a function:

Default[f] = 5;

And then use:

f[a_, b_.] := {a, b}

f[1, 2]
f[1]
   {1, 2}
   {1, 5}

This creates the following Values:

DefaultValues[f]
DownValues[f]
   {HoldPattern[Default[f]] :> 5}
   {HoldPattern[f[a_, b_.]] :> {a, b}}

From this one might think that the value 5 is not fixed in the definition of f, but addresses the DefaultValues assignment. However, if we change the DefaultValues, either directly or using:

Default[f] = 9;

DefaultValues[f]
   {HoldPattern[Default[f]] :> 9}

and use f again:

f[1]
   {1, 5}

we see that the new value is not used.

Therefore, my questions are:

  • Why does the default value used by f[a_, b_.] := {a, b} not change with DefaultValues?

  • Where is the real default value (5) stored, since it does not appear in either DownValues or DefaultValues?

Mr.Wizard
  • 24,179
  • 5
  • 44
  • 125
  • +1 Nice and clear. Let's hope the same for the answers! – Simon Jun 14 '11 at 00:14
  • W: Any reason you aren't defining default values as `f[a_,b_:5]:=...`? – abcd Jun 14 '11 at 00:46
  • @yoda: Because the [idea](http://stackoverflow.com/questions/6322390/compute-distance-in-cartesian-coordinate-system-in-mathematica/6325081#6325081) was to be able to change the default if need be. – Simon Jun 14 '11 at 01:15
  • @yoda Also, if the `Default` value is long and/or used often, it is far cleaner to write `_.`. – Mr.Wizard Jun 14 '11 at 02:35
  • I am adding the [bugs] tag. If one of the WRI devs wants to explain why this is not a bug, I will remove it. – Mr.Wizard Oct 20 '11 at 16:37
  • I see merit in the question, which is why I had already upvoted it 4 months ago. My issue was never with the question, as I'm equally curious as to where and how they're defined. I was merely pointing out that it didn't seem like a bug to me. However, I realize that since the last meaningful discussion was about 4 months ago, I've forgotten exactly what it was about and refreshing my memory from the comments under my answer, I agree with your point. In fact, I even mention in a comment there 4 months ago that it looked like a bug. So, my apologies and comment withdrawn :) – abcd Oct 20 '11 at 18:43
  • @yoda, *my* apologies, I guess I am wearing my cranky-pants today. – Mr.Wizard Oct 20 '11 at 18:46

3 Answers3

10

Not an answer, but:
Using the behaviour that the original default is kept until the function is redefined suggests a quick work-around:

Define a global variable for the Default before any other definitions are made.

In[1]:= Default[f]:=$f
In[2]:= f[a_.]:=a

In[3]:= f[]
Out[3]= $f

In[4]:= $f=5; f[]
Out[5]= 5
In[6]:= $f=6; f[]
Out[7]= 6
In[8]:= $f=.; f[]
Out[9]= $f

This also works for Optional

In[1]:= g[a_:$g] := a

In[2]:= g[]
Out[2]= $g

In[3]:= $g=1; g[]
Out[4]= 1
Simon
  • 14,631
  • 4
  • 41
  • 101
8

From the documentation,

The necessary values for Default[f] must always be defined before _. is used as an argument of f.

Redefining f after setting Default[f] = 9; uses the new default value. So my guess is it is defined internally the first time, f is defined, and doesn't change even if DefaultValue@f stores the new value.

abcd
  • 41,765
  • 7
  • 81
  • 98
  • 3
    But where is it stored internally? The old default doesn't seem to appear in any of the `*Values`... – Simon Jun 14 '11 at 01:17
  • I am sorry, but I do not find this helpful. You seem to have missed both points of my question. – Mr.Wizard Jun 14 '11 at 02:33
  • 2
    @Mr.W: I'm sorry, but this was meant more of a possible explanation rather than a concrete answer (just in case you had missed this part in the documentation). Also, since this is documented behaviour, I don't see the point of the first question (it is what it is). But the second one, I truly do not know and I didn't attempt to answer it. I guess I should've made my intentions clear. – abcd Jun 14 '11 at 03:38
  • 2
    Sorry if that came out rudely. I know others (besides moderators) cannot see my votes, but somehow I felt I needed to explain why I was not voting for this, and I am afraid it came out sounding derogatory. As to `Default`, to me the quote in your answer does not preclude the possibility that `Default[f]` can later be changed and that change will affect earlier uses. (An examination of `DownValues` suggests that this **should** work.) Rather, it tells me that use of `_.` in absence of a defined `Default` may or will register as an error during the assignment. – Mr.Wizard Jun 14 '11 at 05:27
  • 1
    @Mr.W: I see now how that statement can be interpreted the way you mentioned. It certainly seems to be a bug, because I can change the default values of `Plus` and it uses the new defaults without having to redefine it. Try: `Unprotect@Plus; Default[Plus] := 1; Protect@Plus; a /. x_ + y_. :> x + y` – abcd Jun 14 '11 at 14:39
  • 3
    @yoda The problem seems to be with *global* rules. You can try your example with `f` locally: `f[1] /. f[a_, b_.] :> {a, b}` (having first removed its global def), and see that it *is* sensitive to the change of `Default[f]`. My guess is that global rules (definitions) may undergo some internal optimizations at the time the definition is made, which require the value of `Default` at definition-time. After the definition is made, it gets optimized and becomes insensitive to changes of `Default[f]`, unless we have a level of indirection, as in @Simon's answer. – Leonid Shifrin Jun 14 '11 at 17:17
  • 1
    @Leonid Try to define `rule = f[a_, b_.] :> {a, b};` and then evaluate `f[1] /. rule` with different values for `Default[f]`. The output has no any dependence on the latter. – Alexey Popkov Jun 15 '11 at 02:09
  • 1
    @Alexey This behavior is in agreement with what I said. This is a global rule again. Since you use `Set`, the pattern (optional) is evaluated at definition time, which probably triggers the same mechanism as for the l.h.s. patterns. Change `Set` to `SetDelayed` in your example: `rule := f[a_, b_.] :> {a, b}`, and you will recover the sensitivity to `Default[f]`. – Leonid Shifrin Jun 15 '11 at 08:45
  • @Leonid What exactly do you mean by the term "global rule"? I think that rules created by `Set` and `SetDelayed` both are equally *global*. – Alexey Popkov Jun 15 '11 at 11:41
  • @Alexey I think my comment was clear enough. Yes, they are both global, I did not question that. But when you use `Set`, you evaluate both parts at definition-time, while with `SetDelayed`, you postpone the evaluation of r.h.s. until run-time. My guess was that some optimizations happen at definition-time. Your comment might have implied that even for local rules, the effect described by @Mr.W. takes place. My comment was to show that what you observed is again a global rule effect (related to assignments and things happening then), even if the result is a local rule stored in a variable. – Leonid Shifrin Jun 15 '11 at 12:02
  • @Leonid May be it is better to call it "evaluation effect" rather than "global rule effect"? – Alexey Popkov Jun 15 '11 at 12:11
  • 2
    @Alexey It is not clear to me whether this happens for general evaluation or is related to only evaluation of `Set` and `SetDelayed`, and the assignment process. Since I suspect the latter (otherwise it is not clear why the evaluation of optional pattern in a local rule in `f[1] /. f[a_, b_.] :> {a, b}` does not lead to the same `Default`-insensitive behavior), and since `Set` and `SetDelayed` produce global rules, I called it "global rule effect". – Leonid Shifrin Jun 15 '11 at 12:24
  • 1
    @Leonid Good explanation, thank you. Now I see that "evaluation effect" is not the right word. But may be "first evaluation effect" is even better? I feel that there happens something similar to what happens when `RuleDelayed` is evaluated: at first evaluation the expression is verified and some internal information is attached to it that changes its behavior in further evaluations. This idea may be demonstrated with this code: `Default[f]=5;replaceAll[f[1],f[a_,b_.]:>{a,b}]/.(Default[f]=9;replaceAll)->ReplaceAll`. – Alexey Popkov Jun 15 '11 at 12:41
  • @Alexey This is a nice observation, thanks. I like this version more: `f[1] /. {(Default[f] = 5; {f[a_, b_.] :> {a, b}}), (Default[f] = 9; {f[a_, b_.] :> {a, b}})}`. It may be that the default-to-be-used is indeed fixed not at the time `Set` or `SetDelayed` execute but at the time `RuleDelayed` executes. When global rules are created, at some point they are wrapped in `RuleDelayed`, and this is probably when this happens. In my example with `rule:=f[a_, b_.] :> {a, b}`, the `RuleDelayed` on the r.h.s is evaluated very time afresh, and this is probably why it remains `Default`-sensitive. – Leonid Shifrin Jun 15 '11 at 13:07
  • @Alexey But I think that "first evaluation effect" is still too broad. For making the right term, we really need to know better what is happening there. If it is indeed related to evaluation of `RuleDelayed`, one may call it "rule-evaluation effect", although this sounds somewhat obscure without the context of this discussion. – Leonid Shifrin Jun 15 '11 at 13:11
  • @Leonid You are right, see my answer below. `Block`ing `RuleDelayed` changes this behavior to what one could expect. – Alexey Popkov Jun 15 '11 at 13:13
  • @Leonid Now I think that it is better to call it just "`RuleDelayed` effect" since this effect is due to internal functional nature of `RuleDelayed` that is not just a container for rule holding as it looks at first but a function with hidden internal definition. – Alexey Popkov Jun 15 '11 at 13:20
  • @Alexey I would agree if I was sure that the same mechanism exactly holds for global definitions, as in the original question of @Mr.Wizard. It may very well be the case, but note that `Block`-ing `RuleDelayed` and then defining `f` globally inside `Block` does not change things - probably because the code invoked internally when we use `Set` or `SetDelayed` is a low-level version of what we see with local rules. So, I'd consider posting your findings as the answer to the original question a bit too bold - IMO we still don't have a full explanation of what's happening with global definitions. – Leonid Shifrin Jun 15 '11 at 13:28
  • @Leonid Yes, my answer is incomplete but it shows the right direction for thinking and the idea with `Block`ing `RuleDelayed` can potentially be useful. I think it is better not to hide it in comments to an answer where it unlikely will be read. – Alexey Popkov Jun 15 '11 at 13:58
  • @Alexey Sure, I agree, it is better as the answer (otherwise I'd make this comment below your answer, not here :)). I was a bit concerned with the wording, and now that you've changed it, I have no more complaints, and you get my upvote :) – Leonid Shifrin Jun 15 '11 at 14:04
  • 1
    @Leonid It looks like a fault that `Update` does not cover this case. – Alexey Popkov Jun 15 '11 at 14:20
  • @Alexey I agree, from the user's (high-level) viewpoint. From the implementation viewpoint, I am not sure that the cases covered by `Update` are close enough in nature to what we see here - I'd have to know more about both to have a more definite opinion. – Leonid Shifrin Jun 15 '11 at 14:52
3

I have found that this behavior in the case of local rules is due to specifics of internals of RuleDelayed.

Compare:

In[1]:= Default[f] = 5;
replaceAll[f[1], 
  f[a_, b_.] :> Unevaluated@{a, b}] /. (Default[f] = 9; replaceAll) ->
   ReplaceAll

Default[f] = 5;
Block[{RuleDelayed}, 
 replaceAll[f[1], 
   f[a_, b_.] :> Unevaluated@{a, b}] /. (Default[f] = 9; 
    replaceAll) -> ReplaceAll]

Out[2]= {1, 5}

Out[4]= Unevaluated[{1, 9}]

One can see that Blocking RuleDelayed makes local rules to behave as one could expect.

Community
  • 1
  • 1
Alexey Popkov
  • 9,355
  • 4
  • 42
  • 93