Weird behavior of conditions when using OptionsPattern and OptionValue

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.


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

  • How can I create a function with "positional" or "named" optional arguments?

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.