31
$\begingroup$

I have a function that takes an OptionsPattern and I want to access several options, say a, and b. This works great:

f1[OptionsPattern[]] := Block[{},
   {OptionValue[a],OptionValue[b]}
  ];
Options[f1] = {a -> 1, b -> 2};

Calling f1[] returns {1,2} as expected. The problem is that I don't know in advance which options I'll want to access. So I thought about trying this:

f2[OptionsPattern[]] := Block[{list},
   list={a,b};
   OptionValue /@ list
  ];
Options[f2] = {a -> 1, b -> 2};

f2 doesn't work, and neither do f3:

f3[OptionsPattern[]] := Block[{},
   OptionValue /@ {a,b}
  ];

They both return {OptionValue[a], OptionValue[b]}. However, this does work:

f4[OptionsPattern[]] := Block[{},
   OptionValue[#]& /@ {a,b}
  ];

Question: Why on earth do f2,f3 not work, while f4 does, and what is the difference between f4 and f3 anyhow?

PS These also these don't work:

f5[OptionsPattern[]] := Block[{},
  Evaluate[OptionValue /@ {a, b}]
  ];

f6[OptionsPattern[]] := Block[{},
  OptionValue /@ Evaluate[{a, b}]
  ];
$\endgroup$
2
  • 3
    $\begingroup$ Try also f7[OptionsPattern[]] := Block[{}, OptionValue@{a, b}];:) $\endgroup$ Jan 29, 2013 at 12:55
  • $\begingroup$ @belisarius great! $\endgroup$
    – yohbs
    Jan 29, 2013 at 13:10

2 Answers 2

27
$\begingroup$

A guess

My guess is that you have just run into the details of OptionValue implementation, which are also responsible for its "magical" behavior. OptionValue has to somehow know which function it is in, and tracing the execution of f4[] shows that apparently the following expansion is happening before any evaluation is attempted for the r.h.s.:

OptionValue[#]& -> OptionValue[f4,{},#]&

Now, it seems like this (lexical) substitution is triggered when OptionValue[something] is found somewhere on the r.h.s., but not when OptionValue appears as a symbol without arguments. Note that such replacement can not be a part of normal evaluation, but rather looks like a macro-like expansion, based on the lexical analysis of the code on the r.h.s.

Consider this example

d[a -> 8] /. d[OptionsPattern[]] :> HoldComplete@OptionValue[a]
(* HoldComplete[OptionValue[d, {a -> 8}, a]] *)

Now, HoldComplete holds all kinds of dynamic evaluation of the right hand side. However, OptionValue was expanded.

When you are using the magic couple in a way that OptionsPattern has no head, the expansion results in an empty list {} as OptionValue's first argument.

d[a -> 8] /. OptionsPattern[] :> HoldComplete@OptionValue[a]
(* d[HoldComplete[OptionValue[{}, {a -> 8}, a]]] *)

It seems like the OptionsPattern search is done Heads->False and at levels 0 and 1 only. The expansion doesn't happen for lhs such as OptionsPattern[][] or f[g[OptionsPattern[]]. This might be to ensure there is either no or a single head enclosing all the OptionsPattern.

An illustration

Here is some code to mimic this behavior:

Module[{tried},
  Unprotect[SetDelayed];
  SetDelayed[
     f_[args___, optpt : OptionsPattern[]], rhs_
  ] /;!FreeQ[Unevaluated[rhs], optionValue[_]]:=
  Block[{tried = True},
     f[args, optpt] :=
        Unevaluated[rhs] /. optionValue[s_] :> optionValue[f, {optpt}, s]
  ] /; ! TrueQ[tried];
  Protect[SetDelayed];
]

NOTE!! - this redefines SetDelayed(the idea taken from this answer). So now:

f8[OptionsPattern[]]:=Block[{},optionValue[#]&/@{a,b}];
f8[]

(* {optionValue[f8,{},a],optionValue[f8,{},b]} *)

f9[OptionsPattern[]]:=Block[{},optionValue/@{a,b}];
f9[]

(* {optionValue[a],optionValue[b]}  *)

It remains now to define optionValue[f_,opts_List,name_] to make this emulation to work.

Unprotect[SetDelayed];
Clear[SetDelayed];
Protect[SetDelayed]

Remarks

I wouldn't call this behavior consistent, but this is almost certainly a consequence of the internal mechanism (implementation) for the OptionsPattern - OptionValue construct. Whether it is at all possible to make this "magic" fully work in all cases, is IMO a good question.

As to the reason why this does not work for other cases, it looks like OptionValue is dynamically - scoped, in some sense. Only its long (non-magical) form has enough information about the calling function and passed options, to work. At run-time, the short form is evaluated(since the expansion into a long form did not happen), and this short form simply evaluates to itself.

$\endgroup$
7
  • $\begingroup$ My answer didn't add much to this, +1 $\endgroup$
    – Rojo
    Jan 29, 2013 at 13:11
  • $\begingroup$ @Rojo Thanks for the upvote. You'll beat me to it next time :). I added some code to further illustrate things. Please feel free to add to this answer, I think you had some things I don't (local rules, for one). $\endgroup$ Jan 29, 2013 at 13:29
  • 1
    $\begingroup$ @Rojo Thanks! Looks pretty good to me. $\endgroup$ Jan 29, 2013 at 14:51
  • 1
    $\begingroup$ So it looks like this cannot be implemented with standard Mathematica constructs (Stack?). We couldn't make our own OptionValue implementation. Right? $\endgroup$
    – Szabolcs
    Oct 7, 2015 at 17:33
  • 1
    $\begingroup$ @Szabolcs I think we can, but it won't work exactly as OptionValue - but may be close enough to do what you want. Here is one possible (but simplistic) implementation. I think Stack can be used, too. The real OptionValue expands semi-lexically / semi-dynamically at run-time, and surely that was a special case added to the main evaluator, so one probably can't implement that exact behavior in a robust way on the top level, but it must be possible to mimic the main part (get the name of the caller) reasonably well with Stack. $\endgroup$ Oct 7, 2015 at 17:38
0
$\begingroup$

The practical upshot of Leonid's answer seems to be that you can't Map the bare symbol OptionValue but must use an explicit Function form.

So this works:

ClearAll[f1];
Options[f1] = {a -> 1, b -> 2};
f1[OptionsPattern[]] := {OptionValue[a], OptionValue[b]}

And, using Map, so does this:

ClearAll[f2];
Options[f2] = {a -> 1, b -> 2};
f2[OptionsPattern[]] := OptionValue[#] & /@ {a, b}
$\endgroup$
1
  • 6
    $\begingroup$ Well, I stated this in my question. $\endgroup$
    – yohbs
    Jan 29, 2013 at 13:59

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.