8
$\begingroup$

The following innocent-looking code results in error.

Options[add] = {number -> 1};
add[x_, OptionsPattern[]] := Module[{add},
   x + OptionValue[number]
];
add[1]

The error is

OptionValue::optnf: Option name number not found in defaults for add$2832.

What triggers the error is the fact that a local variable declared by Module has the same name as the function, add. (For simplicity, I've shown an example where this local variable isn't actually used in the code, but this doesn't make a difference.)

Is this a bug in Mathematica, or some known quirk of OptionValue[]?

$\endgroup$

1 Answer 1

12
$\begingroup$

Looks like a variable capture, caused by the macro-like expansion mechanism of OptionValue , which happens before the r.h.s. of the function gets evaluated.

What happens

This can be seen if, for example, we wrap the r.h.s. of the function in Hold:

add[x_, OptionsPattern[]] := Hold[Module[{add}, x + OptionValue[number]]]

So that

add[1]

(* Hold[Module[{add$}, 1 + OptionValue[add$, {}, number]]]. *)

Here is my guess about what happens in which order:

  • First, OptionValue expands. At this point we have

     Hold[Module[{add}, 1 + OptionValue[add, {}, number]]]
    

    Note that already at this point, the expansion of OptionValue effectively made the add inside OptionValue[add, {}, number] bind to the local Module variable.

  • Then, RuleDelayed renames add to add$, trying to "protect" the local variable of Module

  • Finally, in the original version without Hold, Module would evaluate, where at that point the local variable inside Module has shadowed the global add symbol in OptionValue[add, {}, number].

So effectively the cavalier macro-style approach of OptionValue expansion mechanism, which pays no attention to lexical scoping, leads to a variable capture (the latter being also a general problem for macros and languages which support them).

What could've happened

Since apparently the expansion mechanism of OptionValue is the very first one to fire in the process of rule application for rules containing OptionsPattern / OptionValue, that mechanism could've renamed the local Module (or other lexical scoping construct) variables before expanding the OptionValue.

That would lead, after OptionValue expansion, to something like

Module[{add$}, 1 + OptionValue[add, {}, number]

and things would've worked then as expected.

But is the current behavior incorrect?

The real problem with this code, to me at least, is that there is no way to refer to the longer form of OptionValue within the Module: anyone who would attempt to do that, would effectively manually perform the same expansion, as done automatically for short form of OptionValue, and would end up with

add[x_, OptionsPattern[]] := Module[{add}, x + OptionValue[add, {}, number]]

So, while the behavior of OptionValue expansion mechanism could perhaps have been smarter - as mentioned above, this problem with 3-arg form of OptionValue shows that at least in part, this is a problem of how the code is written, since this longer form explicitly shows that the local variable shadows the global symbol, and there is no way one could refer to the global symbol without restructuring the code.

From this viewpoint, the behavior of single-argument OptionValue is actually correct.

So is this a bug, and what to do about it

I tend to think that this is not a bug, for reasons discussed in the previous section. I don't even think that the smarter behavior of single-argument OptionValue would've been a good thing, because it would bring about yet more magic: it would make possible to specify the short form of the OptionValue that would bind to the global symbol in the case of shadowing, but not the long form.

To avoid this behavior, simply don't create such collisions. Namely, don't use for variable names the name of the function being defined. It should not be that hard.

$\endgroup$

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.