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.