2
$\begingroup$

I would like to make a function that can perform checks on its option values, and resets them to legal values if necessary. The option should still be accessible using OptionValue. I am looking for a way to do this without touching the function's default option values. A minimal example might look something like this:

ClearAll[f];
Options[f] = {"Opt1" -> 1};

f[opts:OptionsPattern[]] := 
  If[
   OptionValue["Opt1"] < 2, Print["Option value is fine."]; OptionValue["Opt1"],
   Print["Option value is illegal."]; resetOption["Opt1", -1]; OptionValue["Opt1"]
   ];

f["Opt1" -> 4]

This should return -1, but leave the default value at 1. Is there a good way to do this?

EDIT: My real-world function is using OptionValue in several places, and I am looking for a clean way to modify options without having to introduce additional variables. Can this be done?

$\endgroup$
1
$\begingroup$

This will work on your minimal example,

ClearAll[f];
Options[f] = {"Opt1" -> 1};

f[opts : OptionsPattern[]] := If[OptionValue["Opt1"] < 2,
   Print["Option value is fine."];
   OptionValue["Opt1"],
   Print["Option value is illegal."];
   OptionValue["Opt1"] /. {OptionValue["Opt1"] -> -1}
   ];

f["Opt1" -> 4]
Options[f]

"Option value is illegal."
(* -1 *)
(* {"Opt1" -> 1} *)

Sadly, it will not work if you are trying to do anything beyond this minimal example, as for some reason ReplaceAll won't go inside any other Head. That is, if you replace OptionValue["Opt1"] /. {OptionValue["Opt1"] -> -1} with (OptionValue["Opt1"] + 1)/. {OptionValue["Opt1"] -> -1} then the result is 5 and not 0 as you would hope.

$\endgroup$
  • $\begingroup$ Yeah, the function that I am actually going to use is calling OptionValue in multiple instances. It is also passing its options to other functions using FilterRules. If possible I would like to change the option itself, and not the return value of OptionValue. $\endgroup$ – wigg0t Dec 4 '15 at 10:20
  • $\begingroup$ From what I can tell, you can change the option value using SetOptions[f,"Opt1"->-1] inside the function, but this does the exact opposite of what you want - it changes the default option but not the local one. It seems that the local value of OptionValue["Opt1"] is passed to the function when it is called. $\endgroup$ – Jason B. Dec 4 '15 at 10:23
  • 2
    $\begingroup$ Why not use a workaround, putting something like this inside the function: localopt1 = If[OptionValue["Opt1"] < 2, OptionValue["Opt1"], -1] , and then using localopt1 everywhere? $\endgroup$ – Jason B. Dec 4 '15 at 10:24
  • $\begingroup$ This would be the last resort... My real-world function is using OptionValue in several places, so I was more looking for a clean way to modify options without having to recode. I will specify the question accordingly. $\endgroup$ – wigg0t Dec 4 '15 at 11:58
1
$\begingroup$

If, for some reason, you really need to access modified option value using OptionValue["Opt1"], then you could localize OptionValue symbol using Internal`InheritedBlock and assign something to local OptionValue like OptionValue["Opt1"] = something.

An environment that performs necessary localization:

ClearAll[withLocalOptionValue]
SetAttributes[withLocalOptionValue, HoldFirst]
withLocalOptionValue[body_] :=
    Internal`InheritedBlock[{OptionValue},
        Unprotect[OptionValue];
        body
    ]

Now you can wrap whole body of your function with withLocalOptionValue and inside it simply assign values to OptionValue:

ClearAll[f];
Options[f] = {"Opt1" -> 1};
f[OptionsPattern[]] :=
    withLocalOptionValue@If[OptionValue["Opt1"] < 2,
        Print["Option value is fine."];
        OptionValue["Opt1"]
    (* else *),
        Print["Option value is illegal."];
        OptionValue["Opt1"] = -1; 
        OptionValue["Opt1"]
   ]

f["Opt1" -> 4]
(* Option value is illegal. *)
(* -1 *)

f["Opt1" -> 0]
(* Option value is fine. *)
(* 0 *)

But this is a strange requirement, why not simply use Module or With and assign correct option value to local symbol, as suggested in comment by Jason B?

ClearAll[f];
Options[f] = {"Opt1" -> 1};
f[OptionsPattern[]] :=
    Module[{opt1 = OptionValue["Opt1"]},
        If[opt1 < 2,
            Print["Option value is fine."]
        (* else *),
            Print["Option value is illegal."];
            opt1 = -1
        ];
        opt1
    ]

f["Opt1" -> 4]
(* Option value is illegal. *)
(* -1 *)

f["Opt1" -> 0]
(* Option value is fine. *)
(* 0 *)

or

ClearAll[f];
Options[f] = {"Opt1" -> 1};
f[OptionsPattern[]] :=
    With[{opt1 = Replace[OptionValue["Opt1"], val_ /; val >= 2 -> -1]},
        opt1
    ]

f["Opt1" -> 4]
(* -1 *)

f["Opt1" -> 0]
(* 0 *)
$\endgroup$
  • $\begingroup$ The solution using Internal`InheritedBlock is pretty cool! However, for now I'm sticking with the OptionValue[f, options, "Opt1"] solution, because it makes options passing easier. It's nice to have all options in one list and just prepend updated options, because one can pass options from f to g by using a construct like g[FilterRules[{options, Options[f]}, Options[g]]. $\endgroup$ – wigg0t Dec 21 '15 at 9:53
0
$\begingroup$

Ok, found an acceptable solution using the OptionValue[f, opts, name] construct. Thanks!

ClearAll[f];

Options[f] = {"Opt1" -> 1};

f[opts : OptionsPattern[]] := Module[{options},

   options = List[opts];

   If[
    OptionValue[f, options, "Opt1"] < 2,
    Print["Option value is fine."]; OptionValue[f, options, "Opt1"],
    Print["Option value is illegal."]; 
    PrependTo[options, "Opt1" -> -1]; OptionValue[f, options, "Opt1"]
    ]
   ];

f[]
(* Option value is fine. *)
(* 1 *)

f["Opt1" -> 4]
(* Option value is illegal. *)
(* -1 *)
$\endgroup$
  • 1
    $\begingroup$ But in the main function you'll now need to replace all calls to OptionValue["Opt1"] with OptionValue[f, options, "Opt1"] $\endgroup$ – Jason B. Dec 4 '15 at 12:50
  • $\begingroup$ Sorry, my minimal example was possibly too minimal... ;) True, but in my case this solution makes recoding easy, because introduction of that options list makes everything else behave the same as before. For example I can pass options of f to another function g by using a construct like g[FilterRules[{options}, Options[f]}, Options[g]]. Before I have used g[FilterRules[{opts}, Options[f]}, Options[g]]. $\endgroup$ – wigg0t Dec 21 '15 at 10:03

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.