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.
f7[OptionsPattern[]] := Block[{}, OptionValue@{a, b}];
:) $\endgroup$