4

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.

István Zachar
  • 47,032
  • 20
  • 143
  • 291
  • 1
    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. – Leonid Shifrin Jun 16 '21 at 09:18
  • 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? – István Zachar Jun 16 '21 at 10:07
  • It does, but it is not an easy task. If I get some ideas, I will get back here. – Leonid Shifrin Jun 16 '21 at 12:30
  • @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... – István Zachar Jun 16 '21 at 13:59

1 Answers1

4

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} *)

Lukas Lang
  • 33,963
  • 1
  • 51
  • 97