8
$\begingroup$

I'm trying to define a function that is evaluated differently depending on its options. This code

Options[Foo]={Bar->True};
Foo[a_,b_, OptionsPattern[]]:= A /; OptionValue[Bar];
Foo[a_,b_, OptionsPattern[]]:= B /; !OptionValue[Bar];

works as expected. Evaluating

{Foo[x,y,Bar->True],
Foo[x,y,Bar->False],
SetOptions[Foo,Bar->True];
Foo[x,y,Bar->True],
Foo[x,y,Bar->False],
SetOptions[Foo,Bar->False];
Foo[x,y,{Bar->True}],
Foo[x,y,{Bar->False}],
SetOptions[Foo,Bar->True];
Foo[x,y],
SetOptions[Foo,Bar->False];
Foo[x,y]}

I get

{A, B, A, B, A, B, A, B}

which is precisely what I want. However, if I try to make b a blank sequence, i.e.

Options[Foo]={Bar->True};
Foo[a_,b__, OptionsPattern[]]:= A /; OptionValue[Bar];
Foo[a_,b__, OptionsPattern[]]:= B /; !OptionValue[Bar];

weird things start happening. Evaluating my second code sample I get

{A, B, A, A, A, B, A, B}

which is obviously wrong. A possible workaround is to write

Options[Foo]={Bar->True};
Foo[a_,b__/;FreeQ[{b},Rule], OptionsPattern[]]:= A /; OptionValue[Bar];
Foo[a_,b__/;FreeQ[{b},Rule], OptionsPattern[]]:= B /; !OptionValue[Bar];

However this doesn't seem right to me, since it's actually the job of OptionsPattern to fish out the options from my functions. Of course I could also write

Options[Foo]={Bar->True};
Foo[a_,b__, OptionsPattern[]]:= If[OptionValue[Bar],A,B]

which works as well, but this is still another workaround. Moreover, if I want to have something like (in addition to the regular function definition)

Options[Foo]={Bar->True};
Foo[a_,a_,c___, OptionsPattern[]]:= 0 /; OptionValue[Bar];

then running

{Foo[x,x,Bar->True],
Foo[x,x,Bar->False],
SetOptions[Foo,Bar->True];
Foo[x,x,Bar->True],
Foo[x,x,Bar->False],
SetOptions[Foo,Bar->False];
Foo[x,x,{Bar->True}],
Foo[x,x,{Bar->False}],
SetOptions[Foo,Bar->True];
Foo[x,x],
SetOptions[Foo,Bar->False];
Foo[x,x]}

again produces wrong results

{0, Foo[x, x, Bar -> False], 0, 0, 0, Foo[x, x, {Bar -> False}], 0, 
 Foo[x, x]}

and my only solution is to use

Options[Foo]={Bar->True};
Foo[a_,a_,c___/;FreeQ[{c},Rule], OptionsPattern[]]:= 0 /; OptionValue[Bar];

which is kind of a hack.

So my question is, why using conditions with OptionValue and OptionsPattern works if the last pattern before OptionsPattern is a blank, but fails if it is a blank sequence. Is it a bug, or am I missing something essential about OptionsPattern?

I'm observing this behavior both on Mathematca 9.0.1 and Mathematica 10.0.1 under Linux.

$\endgroup$
2
  • $\begingroup$ Is this o.k. for you?Options[Foo] = {Bar -> True}; Foo[a_, b___, c_ /; ! OptionQ[c], OptionsPattern[]] := A /; OptionValue[Bar]; Foo[a_, b___, c_ /; ! OptionQ[c], OptionsPattern[]] := B /; ! OptionValue[Bar]; $\endgroup$ Commented Oct 25, 2014 at 20:34
  • $\begingroup$ @rolf This works, thanks. Now I also understand what was causing the problem. $\endgroup$
    – vsht
    Commented Oct 25, 2014 at 21:23

2 Answers 2

13
$\begingroup$

Note: This is an incomplete analysis and leads to the wrong conclusion about the cause of the difficulty. Mr.W's answer below correctly identifies the culprit as Condition.


The problem you are facing has nothing to do with OptionValue, OptionsPattern, or Condition. It is simply because b__ is under specified and SlotSequence is greedy. Effectively, you have specified

Foo[a_, b__, c___]

so that b__ will pick up everything, including the options, because it hasn't been told not to. The simplest fix is to use Except, e.g.

Foo[a_, b:Except[_?OptionQ].., OptionsPattern[]]

and similarly,

Foo[a_, b_, c:Except[_?OptionQ]..., OptionsPattern[]]

Note the use of ... in the second one.

$\endgroup$
12
  • $\begingroup$ Thanks! This makes perfect sense. I guess I was relying too much on all that magic behind OptionsPattern and OptionValue and started to ignore the basic pattern matching rules. Shame on me. $\endgroup$
    – vsht
    Commented Oct 25, 2014 at 21:24
  • $\begingroup$ By the way, is there a reason why Mathematica 10.0.1 also accepts Foo[a_, b_, _:Except[_?OptionQ]..., OptionsPattern[Foo]] := 0 /; OptionValue[Bar] (however, this function produces wrong results), while Mathematica 9.0.1 immediately complains about Optional::opdef: "The default value for the optional argument _:Except[_?OptionQ]... contains a pattern."? $\endgroup$
    – vsht
    Commented Oct 25, 2014 at 21:51
  • $\begingroup$ @vsht _:Except[_?OptionQ]... is pattern:pattern and that is what it is complaining about. The right hand side of : usually should not contain a pattern, hence the message. $\endgroup$
    – rcollyer
    Commented Oct 26, 2014 at 1:53
  • $\begingroup$ Right, but shouldn't Mathematica 10.0.1 also produce a warning, just like version 9.0.1? $\endgroup$
    – vsht
    Commented Oct 26, 2014 at 18:26
  • 1
    $\begingroup$ @JacobAkkerboom likely OptionsPattern[] is implemented similar to ___?OptionQ with other tricks added to make it function with OptionValue. So, I suspect, with MrW's observations, that when Condition fails, it is shifting values from c to b. $\endgroup$
    – rcollyer
    Commented Oct 27, 2014 at 12:27
6
$\begingroup$

Alright, I took another look at this issue and I do not believe this is a duplicate of:

However I also do not believe that rcollyer's analysis is entirely correct. Please consider this example:

ClearAll[Foo]
Options[Foo] = {Bar -> True};
Foo[a_, b__, OptionsPattern[]] := If[OptionValue[Bar], A, B]

{Foo[x, y, Bar -> True], Foo[x, y, Bar -> False], SetOptions[Foo, Bar -> True];
 Foo[x, y, Bar -> True], Foo[x, y, Bar -> False], SetOptions[Foo, Bar -> False];
 Foo[x, y, {Bar -> True}], Foo[x, y, {Bar -> False}], SetOptions[Foo, Bar -> True];
 Foo[x, y], SetOptions[Foo, Bar -> False];
 Foo[x, y]}
{A, B, A, B, A, B, A, B}

Observe that OptionValue[Bar] resolves correctly to True or False in each case. One can use a more verbose RHS definition to show that every a_ and b__ match is correct and that b__ does not include the option as rcollyer stated:

ClearAll[Foo]
Options[Foo] = {Bar -> True};
Foo[a_, b__, OptionsPattern[]] := {{a}, {b}, OptionValue[Bar]}

{Foo[x, y, Bar -> True], Foo[x, y, Bar -> False], SetOptions[Foo, Bar -> True];
  Foo[x, y, Bar -> True], Foo[x, y, Bar -> False], SetOptions[Foo, Bar -> False];
  Foo[x, y, {Bar -> True}], Foo[x, y, {Bar -> False}], SetOptions[Foo, Bar -> True];
  Foo[x, y], SetOptions[Foo, Bar -> False];
  Foo[x, y]} // MatrixForm

$\left( \begin{array}{ccc} \{x\} & \{y\} & \text{True} \\ \{x\} & \{y\} & \text{False} \\ \{x\} & \{y\} & \text{True} \\ \{x\} & \{y\} & \text{False} \\ \{x\} & \{y\} & \text{True} \\ \{x\} & \{y\} & \text{False} \\ \{x\} & \{y\} & \text{True} \\ \{x\} & \{y\} & \text{False} \\ \end{array} \right)$

Rather I believe the problem you experienced is due the behavior when a Condition fails. One can see that more than one possible match is checked in this example:

ClearAll[Foo]
Options[Foo] = {Bar -> True};
Foo[a_, b__, OptionsPattern[]] := Null /; Print[{{a}, {b}, OptionValue[Bar]}]

Foo[x, y, Bar -> True];
Foo[x, y, {Bar -> False}];

{{x},{y},True}

{{x},{y,Bar->True},True}

{{x},{y},False}

{{x},{y,{Bar->False}},True}

Note that each line results in two different alignments being checked: first the correct one, then an incorrect one. Blocking the incorrect one is how rcollyer's method works, but it is not because b__ is "greedy" but rather because the first, correct alignment did not pass the Condition and another possible alignment is sought. In the second case above the alignment {{x},{y,{Bar->False}},True} is the source of error. (This may be a semantic dispute but I think it is an important one.)

Although I usually favor separate definitions in this case I think using If is a more direct solution without unnecessary additional argument testing.

$\endgroup$
2
  • 1
    $\begingroup$ +1, interesting. It hadn't occurred to me that Condition was the real culprit here. As to favoring separate definitions, I'd agree with you, but instead of the If form, I'd pass it off to a helper function with different definitions depending on the option value. But, I'd pass it as an argument, not an option to that function. $\endgroup$
    – rcollyer
    Commented Oct 27, 2014 at 12:22
  • $\begingroup$ Very interesting indeed. I like the solution with If, but also the one with Except, which I think you explain best. This Q&A has been instructive. $\endgroup$ Commented Oct 27, 2014 at 16:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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