9

Possible Duplicate:
How to Combine Pattern Constraints and Default Values for Function Arguments

First a simple example: define a function "add" with two arguments, and its second argument should be Positive and have a default value 1.

addv1[x_, (y_:1)?Positive] := x + y;
addv2[x_,y?Positive:1] := x + y;

these two just don't work as expected.

So is it impossible to use PatternTest and Optional value on one Pattern simultaneously, considering the probability of its default value conflicting with its pattern test?

ywdr1987
  • 135
  • 5

2 Answers2

22

The syntax

The answer is yes. I use this construct all the time. Here is the form:

add[x_, y : (_?Positive) : 1] := x + y;

You can test that it passes all the test cases.

Sutble behavior to watch out for

There is one additional subtlety associated with this construct: the default value must match the pattern specified for the explicit argument. So, for example, this definition:

Clear[addAuto];
addAuto[x_, y : (_?Positive) : Automatic] := x + y; 

won't work as expected:

addAuto[1]

(* addAuto[1] *)

because Automatic does not match _?Positive. But this will:

Clear[addAuto];
addAuto[x_, y : (_?Positive | Automatic) : Automatic] := x + y;

addAuto[1]

(* 1+Automatic *)

So, make sure that your defualt value matches the explicit arg. pattern. Many, many hours did I waste debugging such cases, more than once. It is not something that first comes to mind. See some more discussion here.

Leonid Shifrin
  • 114,335
  • 15
  • 329
  • 420
  • 1
    Interesting... wouldn't have thought of that! I'll remove the "no" from my answer. This clearly is the winner :) – rm -rf Jan 13 '13 at 15:07
  • @rm-rf I was thinking that this is a common place, since I personally use this all the time (in particuar, I am sure that I used this form in many of my posts here). Apparently, it seems to be not. – Leonid Shifrin Jan 13 '13 at 15:09
  • I'm not surprised that it works though... in fact, immediately after seeing the FullForm in the OP's code, I thought: "Hmm, this might work if I simply rearranged to make Pattern the first object inside Optional" (which is what your grouping does), but then I saw the docs for the error messages and dismissed it without trying =) – rm -rf Jan 13 '13 at 15:13
  • @rm-rf That seems to be a typical situation with the docs for tricky cases - they are literally correct (for most cases), but they may give an impression that something isn't possible while it really is. – Leonid Shifrin Jan 13 '13 at 15:17
  • @rm-rf I added some more stuff here that you may find interesting too. – Leonid Shifrin Jan 13 '13 at 15:29
  • Somehow this seems familiar... perhaps we've had this conversation before (very likely)? Or do you discuss this in your book? I seem to have mentioned this fact to the OP in the duplicate, but it is missing in the accepted answer... Either way, I completely forgot about the duplicate that Mr.Wizard dug up and looking at my answer there and here, it seems like I'm married to the thought of using Condition for such cases =) – rm -rf Jan 13 '13 at 15:39
  • Leonid, I closed this question as a duplicate but as usual your answer goes beyond mine. In fact I made the mistake you warn against in it (though honestly I was aware of the situation, I just failed to point it out). I encourage you to post in that thread. If not, may I have permission to include similar content in my answer? – Mr.Wizard Jan 13 '13 at 15:39
  • 1
    @Mr.Wizard Yes, please do merge my comment into your answer, and then I will delete mine. I completely forgot about that question. – Leonid Shifrin Jan 13 '13 at 15:55
  • 1
    Leonid, I think you should leave this answer undeleted. I've removed my answer from the duplicate instead because the OP changed the example after I pointed it out and so my answer isn't necessary anyway. Mr.Wizard can edit his answer to clarify this subtlety, so that the original has all the bases covered. – rm -rf Jan 13 '13 at 16:00
  • @rm-rf I suggested to delete mainly to have a single place with an exhaustive answer, rather than things scattered over several places. I don't have a strong opinion on the matter, though - can keep it as well. – Leonid Shifrin Jan 13 '13 at 16:26
  • I used this construct yesterday, even stopping to check out how it worked when the default argument didn't match the test, and ended up with the same Alternative (None instead of Automatic). Small world – Rojo Jan 13 '13 at 17:33
  • @Rojo Re: small world - very true. – Leonid Shifrin Jan 13 '13 at 18:02
  • @rm-rf Somehow I overlooked your comment above. We might have discussed that indeed - I also have some fading memories of such a discussion. I also have an impression that I already posted some answer before where I stressed this subtlety with default value having to match the explicit pattern, but I don't remember where. Re: Condition - I used to use it a lot before, but now I somehow prefer PatternTest when possible. Usually, I use Condition either when the test condition has to keep the expression being matched unevaluated, or when the condition involves several pattern variables. – Leonid Shifrin Jan 13 '13 at 19:11
7

The two definitions you used don't work because

  • Optional cannot be used as the first argument in PatternTest, thus ruling out addv1. This is mentioned in the documentation for General::patop:

    A pattern based on Optional cannot be used as the first argument in PatternTest, Condition, Repeated, RepeatedNull, or Optional, or as the second argument in Pattern.

  • Optional requires a Blank[] as the optional object and gives a General::optb error otherwise, thus ruling out addv2

However, you can still retain the expressiveness of the single line definition that you had hoped would work by using Condition instead as:

Clear@add
add[x_, y_: 1] /; Positive@y := x + y

and it works as expected:

add[1]
(* 2 *)

add[1, 2] (* 3 *)

add[1, a] (* add[1, a] *)

rm -rf
  • 88,781
  • 21
  • 293
  • 472