5
$\begingroup$

Mathematica is behaving in an expected way when passing a parameter with an Optional and a Sequence. Here is an example:

ClearAll[h];
h[x_: 0.1, opts__] := (Print["x:", x, "\nopts:", opts];"Fish")/; (! TrueQ["Save" /. Flatten@List@opts]);

Evaluating

In := h[.2, "Save"->False] 

I get what I would expect: the condition (! TrueQ["Save" /. Flatten@List@opts]) is true so h returns

     "x:"0.2
     "opts:""Save"->False
Out= "Fish"

However, when evaluating

In:= h[.2, "Save"->True] 

I get an unexpected behaviour:

     ReplaceAll::reps: {0.2,Save->True} is neither a list of replacement rules nor a valid dispatch table, and so cannot be used for replacing. >>
     "x:"0.2
     "opts:""Save"->False
Out= "Fish"

By taking Trace I could see that Mathematica first checks that the condition is false when x=0.2 and opts = "Save"->True, but instead of returning h[.2, "Save"->True] unevaluated (as I would expect), it takes the Optional value for x (x=0.1), and then interprets opts = Sequence[0.2, "Save"->False], which to me is bonkers!

Shouldn't Optional values only be taken when the parameter is not passed explicitly? Is this not an undesired behaviour?

Also, please, what would be a good work around? Thanks for reading!

$\endgroup$
2
  • $\begingroup$ I removed the term "operator overloading" from the title because I found it confusing. "Operator overloading" has a different meaning: changing the meaning of an operator (like +) depending on the kinds of operands, e.g. in some languages + can be used with strings and it concatenates them. It also can be used for numbers and it adds them. Thus it's "overloaded" with two separate meanings. $\endgroup$
    – Szabolcs
    Feb 18, 2016 at 17:26
  • $\begingroup$ @Szabolcs, thanks for the changes, using the Match is clearer. @Xavier the TrueQ is there so that definition matches whenever "Save" is not True, rather than only matching when "Save" is False. Cheers $\endgroup$
    – Art Gower
    Feb 19, 2016 at 15:54

1 Answer 1

7
$\begingroup$

How to define such functions

If you want to define a function that has optional arguments and also takes (zero or more) options, a good way to do it is to make patterns specific and ensure that no unexpected matches will happen.

I like to do something like this:

f[x : (_?NumericQ) : 0.1, opt : OptionsPattern[]] := ...

We want to make sure that:

  • opt will never match anything that may be meant by the user as the optional argument. Thus we allow only options (i.e. rules, but not numbers) by using OptionsPattern[].

  • x will never match any option. Thus we restrict it to something that cannot possibly match an option (i.e. _Rule or _RuleDelayed). If x must be a number, this can be NumericQ.


Why does your example behave the way it does?

The condition (/;) you added at the end is really part of the pattern. Mathematica keeps trying different matches for the whole pattern until one succeeds. Since the first argument is optional, it is allowed to be part of the match and it is also allowed not to be part of the match.

First Mathematica tries one option: use x_ for the match, thus x is 0.2 now. The condition fails, so Mathematica tries another possibility: let x take the default value and not be part of the match. Now 0.2 must go in opts, and then it causes a failure in the condition code. This failure also happens to cause the condition to be True, so a match is found and the search stops.

It's easier to see what goes on if you add some Prints in the condition itself.

In[54]:= ClearAll[h];
h[x_: 0.1, opts__] := (
   Print["x:", x, "\nopts:", opts];
   "Fish"
   ) /; (Print[{{x}, {opts}}]; ! TrueQ["Save" /. Flatten@List@opts])

In[56]:= h[.2, "Save" -> True]

During evaluation of In[56]:= {{0.2},{Save->True}}

During evaluation of In[56]:= {{0.1},{0.2,Save->True}}

During evaluation of In[56]:= ReplaceAll::reps: {0.2,Save->True} is neither a list of replacement rules nor a valid dispatch table, and so cannot be used for replacing. >>

During evaluation of In[56]:= x:0.1
opts:0.2Save->True

Out[56]= "Fish"

Shouldn't Optional values only be taken when the parameter is not passed explicitly? Is this not an undesired behaviour?

The point I am trying to make is that as far as the pattern matcher is concerned, "the parameter wasn't passed". More precisely, it considers the pattern as a whole, and always looks whether it matches or not as a whole. If it tries to match x_ with 0.2 and leaves the rest of the sequence for opts__ then the entire pattern won't match. So it needs to try the other alternative where x takes the default value (and is left out of the matching) and everything goes to opts__.

There's also to consider what would happen if the function allows zero options and you'd call f[SomeOption -> something]. Should x match SomeOption -> something or should it take the default value of 0.1? By default x will just match SomeOption -> something unless the pattern is written in a way as to make this impossible (see first section describing workarounds).

$\endgroup$
1
  • $\begingroup$ I think that if we go the OptionsPattern route we are required to set Options[f] = "Save" -> True. I was not successful without this additional piece of code. $\endgroup$ Feb 18, 2016 at 18:20

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.