4
$\begingroup$

I want to refer to the actual option values of a function f outside of f, using OptionValue. Since I would use the same reference in different function f, g, etc., each resolving to its own actual option values, I do not want to specify the function name f in the external OptionValue call.

OptionValue["opt"] is not resolved to OptionValue[f, "opt"] inside f. Can I circumvent this without explicitly stating OptionValue[f, "opt"] in the external assoc? BTW, that wont' work either, returning the default 1 instead of 2.

ClearAll[f];
Options[f] = {"opt" -> 1};
f[x_, OptionsPattern[]] := {
     OptionValue["opt"],     (* This is evaluated correctly *)
     x["Option"],            (* This is not resolved correctly *)
     Evaluate[x["Option"]]   (* This is not resolved correctly *)
  };

assoc = <|"Option" :> OptionValue["opt"]|>

f[assoc, "opt" -> 2]

Output:

{2, OptionValue["opt"], OptionValue["opt"]}

My expected result would be {2, 1, 1}, though I understand that the special nature of OptionValue prevents the kernel to resolve OptionValue["opt"] to OptionValue[f, "opt"] at the time it is first encountered.

$\endgroup$
4
  • 1
    $\begingroup$ If you look at this answer, you will see that OptionValue is a magic head, which gets expanded to 3 - argument form before the r.h.s. of a function is evaluated. Which would mean that you can't accomplish directly what you are after - of course unless you use the 3-arg form of OptionValue directly. But, what is the actual need / problem? It looks like you would like to hide one option behind some other, such that both work, but only one is exposed / documented. If that is the case, I can suggest a way which I've used in such cases. $\endgroup$ Jun 16 at 9:18
  • $\begingroup$ Thanks @Leonid for the explanation. I have a set of functions, like f1, f2, ..., which use roughly the same set of options, but with different values. I want to set up a global association assoc that can be used by any of the f_i, and references to options in assoc would magically be resolved to the actual option values of the f_i that uses it. The point is, that assoc is the one my clients should interact with (define), and not the f_i, so I want to make option-reference as trivial as possible, without introducing global symbols for all option values. Does it clarify my aim? $\endgroup$ Jun 16 at 10:07
  • $\begingroup$ It does, but it is not an easy task. If I get some ideas, I will get back here. $\endgroup$ Jun 16 at 12:30
  • $\begingroup$ @Leonid I appreciate your effort. I want to add, that whatever is used ultimately, it has to be domestic in the sense that I cannot expect the user to use anything more obscure than OptionValue to define parts of assoc (which holds more key-value pairs in reality). Moreover, I have nested options... $\endgroup$ Jun 16 at 13:59
4
$\begingroup$

We can (ab)use the automatic expansion of OptionValue into its three-argument-form, which works even inside rules:

Options[func] = {"opt" -> None};
{func["opt" -> 1], func[]} /. func[OptionsPattern[]] :> OptionValue["opt"]
(* {1, None} *)

Now, for your example: First, we define iExpandOptionValue that expands OptionValue into a list of expressions using the trick above.

Attributes[iExpandOptionValue] = {HoldAll};
iExpandOptionValue[OptionValue[head_, opts_, __], vars___] :=
 Hold[vars] /. <|args___|> :> <|args|> /. Hold[evars___] :> (
    Unevaluated@head[opts] /. HoldPattern@head[OptionsPattern[]] :> {evars}
    )

Note the /. <|args___|> :> <|args|> trick. This makes sure that associations inside vars are not initialized, since otherwise the replacement doesn't work as expected.

Next, we define ExpandOptionValue. It is used as the right side of a definition, where the first argument specifies variables to "expand" using the function above, and the second argument is the expression to evaluate.

HoldPattern[lhs_ := ExpandOptionValue[{vars___}, rhs_]] ^:=
 Replace[
  {Hold[rhs], Quiet@Replace[Hold[vars], var_ :> Pattern[var, _], 1]},
  {Hold[rhs2_], Hold[pats___]} :> (
    lhs := iExpandOptionValue[OptionValue["dummy"], vars] /. {pats} :> rhs2
    )
  ]

We use a similar trick to above to extract the function on the left side using OptionValue["dummy"], which we can then pass to iExpandOptionValue. After some more replacements, we are finally done. We can now use this on your example:

ClearAll[f];
Options[f] = {"opt" -> 1};
f[x_, OptionsPattern[]] := ExpandOptionValue[{x},
   x["Option"]
   ];

assoc = <|"Option" :> OptionValue["opt"]|>;

f[assoc, "opt" -> 2]
(* 2 *)

To see a bit what's going on, we can look at the definition of f:

Definition@f
(* f[x$_,OptionsPattern[]]:=iExpandOptionValue[OptionValue[dummy],x$]/. {x_}:>x[Option]
 
Options[f]={opt->1} *)
$\endgroup$
2
  • $\begingroup$ +1. I planned to try something along similar lines, but now I have no need to :) $\endgroup$ Jun 16 at 16:25
  • $\begingroup$ This seems be doing what it should, thanks Lukas. Let me test it a bit more with production code, and accept if stable. $\endgroup$ Jun 18 at 7:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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