Take the 2-minute tour ×
Mathematica Stack Exchange is a question and answer site for users of Mathematica. It's 100% free, no registration required.

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}]
  ];
share|improve this question
    
Try also f7[OptionsPattern[]] := Block[{}, OptionValue@{a, b}];:) –  belisarius Jan 29 '13 at 12:55
    
@belisarius great! –  yohbs Jan 29 '13 at 13:10
add comment

2 Answers

up vote 17 down vote accepted

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.

share|improve this answer
    
My answer didn't add much to this, +1 –  Rojo Jan 29 '13 at 13:11
    
@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). –  Leonid Shifrin Jan 29 '13 at 13:29
    
Edited. Feel free to roll back –  Rojo Jan 29 '13 at 14:33
1  
@Rojo Thanks! Looks pretty good to me. –  Leonid Shifrin Jan 29 '13 at 14:51
add comment

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}
share|improve this answer
4  
Well, I stated this in my question. –  yohbs Jan 29 '13 at 13:59
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.