Renaming options of custom functions while preserving backwards compatibility
Clash Royale CLAN TAG#URR8PPP
up vote
3
down vote
favorite
I have a package with many functions. There is an option name that several functions share. I feel that the current name of this option was not the best choice. I would like to rename it, but at the same time, I would like to preserve backwards compatibility.
If this option name were a symbol, the simple solution would be
oldName = newName
(And, of course, replacing all occurrences of the old name with the new one in the package.)
But in this case, the option name is a string. Even if it weren't, OptionValue
does not distinguish between string and symbol names. It is preferable to handle both.
The package uses the standard option handling with OptionsPattern
/OptionValue
.
What is the best way to rename this option? I am looking to change as little existing code as possible (except for basic find-and-replace of names), and take as little performance hit as possible.
options
add a comment |Â
up vote
3
down vote
favorite
I have a package with many functions. There is an option name that several functions share. I feel that the current name of this option was not the best choice. I would like to rename it, but at the same time, I would like to preserve backwards compatibility.
If this option name were a symbol, the simple solution would be
oldName = newName
(And, of course, replacing all occurrences of the old name with the new one in the package.)
But in this case, the option name is a string. Even if it weren't, OptionValue
does not distinguish between string and symbol names. It is preferable to handle both.
The package uses the standard option handling with OptionsPattern
/OptionValue
.
What is the best way to rename this option? I am looking to change as little existing code as possible (except for basic find-and-replace of names), and take as little performance hit as possible.
options
add a comment |Â
up vote
3
down vote
favorite
up vote
3
down vote
favorite
I have a package with many functions. There is an option name that several functions share. I feel that the current name of this option was not the best choice. I would like to rename it, but at the same time, I would like to preserve backwards compatibility.
If this option name were a symbol, the simple solution would be
oldName = newName
(And, of course, replacing all occurrences of the old name with the new one in the package.)
But in this case, the option name is a string. Even if it weren't, OptionValue
does not distinguish between string and symbol names. It is preferable to handle both.
The package uses the standard option handling with OptionsPattern
/OptionValue
.
What is the best way to rename this option? I am looking to change as little existing code as possible (except for basic find-and-replace of names), and take as little performance hit as possible.
options
I have a package with many functions. There is an option name that several functions share. I feel that the current name of this option was not the best choice. I would like to rename it, but at the same time, I would like to preserve backwards compatibility.
If this option name were a symbol, the simple solution would be
oldName = newName
(And, of course, replacing all occurrences of the old name with the new one in the package.)
But in this case, the option name is a string. Even if it weren't, OptionValue
does not distinguish between string and symbol names. It is preferable to handle both.
The package uses the standard option handling with OptionsPattern
/OptionValue
.
What is the best way to rename this option? I am looking to change as little existing code as possible (except for basic find-and-replace of names), and take as little performance hit as possible.
options
options
asked 1 hour ago
Szabolcs
155k13420911
155k13420911
add a comment |Â
add a comment |Â
2 Answers
2
active
oldest
votes
up vote
1
down vote
You can try the following:
Attributes[HandleLegacyOption] = HoldAll;
HandleLegacyOption[o : OptionValue[sym_, opts_, name_]] :=
o //. Fallback[old_] :> OptionValue[sym, opts, old]
This can be used the following way:
Options[f] = "bar" -> Fallback["foo"], "foo" -> 1;
f[OptionsPattern] := HandleLegacyOption@OptionValue["bar"]
f
f["bar" -> 2]
f["foo" -> 2]
f["foo" -> 2, "bar" -> 3]
(* 1 *)
(* 2 *)
(* 2 *)
(* 3 *)
As you can see, setting either option works, and the new name takes precedence over the old one.
How?
Since OptionValue
is a very special function, we can't do much other than explicitly leaving OptionValue[…]
in the r.h.s. of our definitions. But one thing we can use is the fact that OptionValue[…]
constructs are always expanded, no matter where they appear (see also linked question):
g[OptionsPattern] := Hold@OptionValue["foo"]
g["bar" -> 1]
(* Hold[OptionValue[g, "bar" -> 1, "foo"]] *)
So as long as we have OptionValue[…]
explicitly appearing, we have access to:
- The symbol, and thus the defaults
- The explicitly specified options
- The queried options
The function HandleLegacyOption
above uses this information by repeatedly querying option values as long as the result is Fallback[oldName]
. This essentially defaults the new option to the value of another option.
Possible extensions
As mentioned earlier, we need OptionValue
to appear on the r.h.s. of the definition, otherwise we won't get the automatic expansion of all the information we need. One possible way to (partially) automate this wrapping of OptionValue
might be:
HoldPattern[lhs_ // AddLegacyOptionHandling := rhs_] ^:=
Hold[rhs] /.
o_OptionValue :> HandleLegacyOption@o /.
Hold[proc_] :> (lhs := proc)
This automatically wraps all OptionValue
expressions on the r.h.s. in HandleLegacyOption
, e.g.
f[OptionsPattern] // AddLegacyOptionHandling := OptionValue["bar"]
yields the same result as in the first example.
add a comment |Â
up vote
1
down vote
This is my new approach. It is minimal invasive in the sense that it has to redefine OptionValue
to handle only the new option "newopt"
differently:
optionAliases = <|"newopt" -> "oldopt"|>;
Unprotect[OptionValue];
OptionValue[f_, opts_, "newopt"] := If[
("newopt" /. opts) =!= "newopt",
First[OptionValue[f, opts, "newopt"]],
First[OptionValue[f, opts, optionAliases["newopt"]]] /.
Automatic :> First[OptionValue[f, opts, "newopt"]]
];
Protect[OptionValue];
Now declare a function f
in the classical way, but set the defaults for all "old" options to Automatic
(or to another custom symbol):
ClearAll[f];
Options[f] =
"newopt" -> 1,
"oldopt" -> Automatic
;
f[opts : OptionsPattern] := OptionValue["newopt"]
Let's see what happens:
f
f["newopt" -> 2]
f["oldopt" -> 3]
f["newopt" -> 4, "oldopt" -> 3]
1
2
3
4
Note that we rely on the fact that OptionValue
will treat OptionValue[f, opts, "newopt"]
as before. So this will only work if you call OptionValue["newopt"]
, not if you request it with several option values at once like in OptionValue["opt1", "opt2", ... "newopt", ... ]
. One might be able to make it work by specifying an additional rule àla
OptionValue[
f_,
opts_,
list_List?(x [Function] Length[list] >= 2 && MemberQ[list, "newopt"])
] := ...
But I am afraid that this will slow down the overall system.
Of course, you can also specify special behavior only for selected functions with
Unprotect[OptionValue];
OptionValue[f, opts_, "newopt"] := ...
OptionValue[h, opts_, "newopt"] := ...
Protect[OptionValue];
making this somewhat more robust. I also tried TagSet
f /: OptionValue[f, opts_, "newopt"] := ...
but this wouldn't work as OptionValue
still branches to its conventional definition this way.
add a comment |Â
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
1
down vote
You can try the following:
Attributes[HandleLegacyOption] = HoldAll;
HandleLegacyOption[o : OptionValue[sym_, opts_, name_]] :=
o //. Fallback[old_] :> OptionValue[sym, opts, old]
This can be used the following way:
Options[f] = "bar" -> Fallback["foo"], "foo" -> 1;
f[OptionsPattern] := HandleLegacyOption@OptionValue["bar"]
f
f["bar" -> 2]
f["foo" -> 2]
f["foo" -> 2, "bar" -> 3]
(* 1 *)
(* 2 *)
(* 2 *)
(* 3 *)
As you can see, setting either option works, and the new name takes precedence over the old one.
How?
Since OptionValue
is a very special function, we can't do much other than explicitly leaving OptionValue[…]
in the r.h.s. of our definitions. But one thing we can use is the fact that OptionValue[…]
constructs are always expanded, no matter where they appear (see also linked question):
g[OptionsPattern] := Hold@OptionValue["foo"]
g["bar" -> 1]
(* Hold[OptionValue[g, "bar" -> 1, "foo"]] *)
So as long as we have OptionValue[…]
explicitly appearing, we have access to:
- The symbol, and thus the defaults
- The explicitly specified options
- The queried options
The function HandleLegacyOption
above uses this information by repeatedly querying option values as long as the result is Fallback[oldName]
. This essentially defaults the new option to the value of another option.
Possible extensions
As mentioned earlier, we need OptionValue
to appear on the r.h.s. of the definition, otherwise we won't get the automatic expansion of all the information we need. One possible way to (partially) automate this wrapping of OptionValue
might be:
HoldPattern[lhs_ // AddLegacyOptionHandling := rhs_] ^:=
Hold[rhs] /.
o_OptionValue :> HandleLegacyOption@o /.
Hold[proc_] :> (lhs := proc)
This automatically wraps all OptionValue
expressions on the r.h.s. in HandleLegacyOption
, e.g.
f[OptionsPattern] // AddLegacyOptionHandling := OptionValue["bar"]
yields the same result as in the first example.
add a comment |Â
up vote
1
down vote
You can try the following:
Attributes[HandleLegacyOption] = HoldAll;
HandleLegacyOption[o : OptionValue[sym_, opts_, name_]] :=
o //. Fallback[old_] :> OptionValue[sym, opts, old]
This can be used the following way:
Options[f] = "bar" -> Fallback["foo"], "foo" -> 1;
f[OptionsPattern] := HandleLegacyOption@OptionValue["bar"]
f
f["bar" -> 2]
f["foo" -> 2]
f["foo" -> 2, "bar" -> 3]
(* 1 *)
(* 2 *)
(* 2 *)
(* 3 *)
As you can see, setting either option works, and the new name takes precedence over the old one.
How?
Since OptionValue
is a very special function, we can't do much other than explicitly leaving OptionValue[…]
in the r.h.s. of our definitions. But one thing we can use is the fact that OptionValue[…]
constructs are always expanded, no matter where they appear (see also linked question):
g[OptionsPattern] := Hold@OptionValue["foo"]
g["bar" -> 1]
(* Hold[OptionValue[g, "bar" -> 1, "foo"]] *)
So as long as we have OptionValue[…]
explicitly appearing, we have access to:
- The symbol, and thus the defaults
- The explicitly specified options
- The queried options
The function HandleLegacyOption
above uses this information by repeatedly querying option values as long as the result is Fallback[oldName]
. This essentially defaults the new option to the value of another option.
Possible extensions
As mentioned earlier, we need OptionValue
to appear on the r.h.s. of the definition, otherwise we won't get the automatic expansion of all the information we need. One possible way to (partially) automate this wrapping of OptionValue
might be:
HoldPattern[lhs_ // AddLegacyOptionHandling := rhs_] ^:=
Hold[rhs] /.
o_OptionValue :> HandleLegacyOption@o /.
Hold[proc_] :> (lhs := proc)
This automatically wraps all OptionValue
expressions on the r.h.s. in HandleLegacyOption
, e.g.
f[OptionsPattern] // AddLegacyOptionHandling := OptionValue["bar"]
yields the same result as in the first example.
add a comment |Â
up vote
1
down vote
up vote
1
down vote
You can try the following:
Attributes[HandleLegacyOption] = HoldAll;
HandleLegacyOption[o : OptionValue[sym_, opts_, name_]] :=
o //. Fallback[old_] :> OptionValue[sym, opts, old]
This can be used the following way:
Options[f] = "bar" -> Fallback["foo"], "foo" -> 1;
f[OptionsPattern] := HandleLegacyOption@OptionValue["bar"]
f
f["bar" -> 2]
f["foo" -> 2]
f["foo" -> 2, "bar" -> 3]
(* 1 *)
(* 2 *)
(* 2 *)
(* 3 *)
As you can see, setting either option works, and the new name takes precedence over the old one.
How?
Since OptionValue
is a very special function, we can't do much other than explicitly leaving OptionValue[…]
in the r.h.s. of our definitions. But one thing we can use is the fact that OptionValue[…]
constructs are always expanded, no matter where they appear (see also linked question):
g[OptionsPattern] := Hold@OptionValue["foo"]
g["bar" -> 1]
(* Hold[OptionValue[g, "bar" -> 1, "foo"]] *)
So as long as we have OptionValue[…]
explicitly appearing, we have access to:
- The symbol, and thus the defaults
- The explicitly specified options
- The queried options
The function HandleLegacyOption
above uses this information by repeatedly querying option values as long as the result is Fallback[oldName]
. This essentially defaults the new option to the value of another option.
Possible extensions
As mentioned earlier, we need OptionValue
to appear on the r.h.s. of the definition, otherwise we won't get the automatic expansion of all the information we need. One possible way to (partially) automate this wrapping of OptionValue
might be:
HoldPattern[lhs_ // AddLegacyOptionHandling := rhs_] ^:=
Hold[rhs] /.
o_OptionValue :> HandleLegacyOption@o /.
Hold[proc_] :> (lhs := proc)
This automatically wraps all OptionValue
expressions on the r.h.s. in HandleLegacyOption
, e.g.
f[OptionsPattern] // AddLegacyOptionHandling := OptionValue["bar"]
yields the same result as in the first example.
You can try the following:
Attributes[HandleLegacyOption] = HoldAll;
HandleLegacyOption[o : OptionValue[sym_, opts_, name_]] :=
o //. Fallback[old_] :> OptionValue[sym, opts, old]
This can be used the following way:
Options[f] = "bar" -> Fallback["foo"], "foo" -> 1;
f[OptionsPattern] := HandleLegacyOption@OptionValue["bar"]
f
f["bar" -> 2]
f["foo" -> 2]
f["foo" -> 2, "bar" -> 3]
(* 1 *)
(* 2 *)
(* 2 *)
(* 3 *)
As you can see, setting either option works, and the new name takes precedence over the old one.
How?
Since OptionValue
is a very special function, we can't do much other than explicitly leaving OptionValue[…]
in the r.h.s. of our definitions. But one thing we can use is the fact that OptionValue[…]
constructs are always expanded, no matter where they appear (see also linked question):
g[OptionsPattern] := Hold@OptionValue["foo"]
g["bar" -> 1]
(* Hold[OptionValue[g, "bar" -> 1, "foo"]] *)
So as long as we have OptionValue[…]
explicitly appearing, we have access to:
- The symbol, and thus the defaults
- The explicitly specified options
- The queried options
The function HandleLegacyOption
above uses this information by repeatedly querying option values as long as the result is Fallback[oldName]
. This essentially defaults the new option to the value of another option.
Possible extensions
As mentioned earlier, we need OptionValue
to appear on the r.h.s. of the definition, otherwise we won't get the automatic expansion of all the information we need. One possible way to (partially) automate this wrapping of OptionValue
might be:
HoldPattern[lhs_ // AddLegacyOptionHandling := rhs_] ^:=
Hold[rhs] /.
o_OptionValue :> HandleLegacyOption@o /.
Hold[proc_] :> (lhs := proc)
This automatically wraps all OptionValue
expressions on the r.h.s. in HandleLegacyOption
, e.g.
f[OptionsPattern] // AddLegacyOptionHandling := OptionValue["bar"]
yields the same result as in the first example.
answered 56 mins ago


Lukas Lang
5,3081525
5,3081525
add a comment |Â
add a comment |Â
up vote
1
down vote
This is my new approach. It is minimal invasive in the sense that it has to redefine OptionValue
to handle only the new option "newopt"
differently:
optionAliases = <|"newopt" -> "oldopt"|>;
Unprotect[OptionValue];
OptionValue[f_, opts_, "newopt"] := If[
("newopt" /. opts) =!= "newopt",
First[OptionValue[f, opts, "newopt"]],
First[OptionValue[f, opts, optionAliases["newopt"]]] /.
Automatic :> First[OptionValue[f, opts, "newopt"]]
];
Protect[OptionValue];
Now declare a function f
in the classical way, but set the defaults for all "old" options to Automatic
(or to another custom symbol):
ClearAll[f];
Options[f] =
"newopt" -> 1,
"oldopt" -> Automatic
;
f[opts : OptionsPattern] := OptionValue["newopt"]
Let's see what happens:
f
f["newopt" -> 2]
f["oldopt" -> 3]
f["newopt" -> 4, "oldopt" -> 3]
1
2
3
4
Note that we rely on the fact that OptionValue
will treat OptionValue[f, opts, "newopt"]
as before. So this will only work if you call OptionValue["newopt"]
, not if you request it with several option values at once like in OptionValue["opt1", "opt2", ... "newopt", ... ]
. One might be able to make it work by specifying an additional rule àla
OptionValue[
f_,
opts_,
list_List?(x [Function] Length[list] >= 2 && MemberQ[list, "newopt"])
] := ...
But I am afraid that this will slow down the overall system.
Of course, you can also specify special behavior only for selected functions with
Unprotect[OptionValue];
OptionValue[f, opts_, "newopt"] := ...
OptionValue[h, opts_, "newopt"] := ...
Protect[OptionValue];
making this somewhat more robust. I also tried TagSet
f /: OptionValue[f, opts_, "newopt"] := ...
but this wouldn't work as OptionValue
still branches to its conventional definition this way.
add a comment |Â
up vote
1
down vote
This is my new approach. It is minimal invasive in the sense that it has to redefine OptionValue
to handle only the new option "newopt"
differently:
optionAliases = <|"newopt" -> "oldopt"|>;
Unprotect[OptionValue];
OptionValue[f_, opts_, "newopt"] := If[
("newopt" /. opts) =!= "newopt",
First[OptionValue[f, opts, "newopt"]],
First[OptionValue[f, opts, optionAliases["newopt"]]] /.
Automatic :> First[OptionValue[f, opts, "newopt"]]
];
Protect[OptionValue];
Now declare a function f
in the classical way, but set the defaults for all "old" options to Automatic
(or to another custom symbol):
ClearAll[f];
Options[f] =
"newopt" -> 1,
"oldopt" -> Automatic
;
f[opts : OptionsPattern] := OptionValue["newopt"]
Let's see what happens:
f
f["newopt" -> 2]
f["oldopt" -> 3]
f["newopt" -> 4, "oldopt" -> 3]
1
2
3
4
Note that we rely on the fact that OptionValue
will treat OptionValue[f, opts, "newopt"]
as before. So this will only work if you call OptionValue["newopt"]
, not if you request it with several option values at once like in OptionValue["opt1", "opt2", ... "newopt", ... ]
. One might be able to make it work by specifying an additional rule àla
OptionValue[
f_,
opts_,
list_List?(x [Function] Length[list] >= 2 && MemberQ[list, "newopt"])
] := ...
But I am afraid that this will slow down the overall system.
Of course, you can also specify special behavior only for selected functions with
Unprotect[OptionValue];
OptionValue[f, opts_, "newopt"] := ...
OptionValue[h, opts_, "newopt"] := ...
Protect[OptionValue];
making this somewhat more robust. I also tried TagSet
f /: OptionValue[f, opts_, "newopt"] := ...
but this wouldn't work as OptionValue
still branches to its conventional definition this way.
add a comment |Â
up vote
1
down vote
up vote
1
down vote
This is my new approach. It is minimal invasive in the sense that it has to redefine OptionValue
to handle only the new option "newopt"
differently:
optionAliases = <|"newopt" -> "oldopt"|>;
Unprotect[OptionValue];
OptionValue[f_, opts_, "newopt"] := If[
("newopt" /. opts) =!= "newopt",
First[OptionValue[f, opts, "newopt"]],
First[OptionValue[f, opts, optionAliases["newopt"]]] /.
Automatic :> First[OptionValue[f, opts, "newopt"]]
];
Protect[OptionValue];
Now declare a function f
in the classical way, but set the defaults for all "old" options to Automatic
(or to another custom symbol):
ClearAll[f];
Options[f] =
"newopt" -> 1,
"oldopt" -> Automatic
;
f[opts : OptionsPattern] := OptionValue["newopt"]
Let's see what happens:
f
f["newopt" -> 2]
f["oldopt" -> 3]
f["newopt" -> 4, "oldopt" -> 3]
1
2
3
4
Note that we rely on the fact that OptionValue
will treat OptionValue[f, opts, "newopt"]
as before. So this will only work if you call OptionValue["newopt"]
, not if you request it with several option values at once like in OptionValue["opt1", "opt2", ... "newopt", ... ]
. One might be able to make it work by specifying an additional rule àla
OptionValue[
f_,
opts_,
list_List?(x [Function] Length[list] >= 2 && MemberQ[list, "newopt"])
] := ...
But I am afraid that this will slow down the overall system.
Of course, you can also specify special behavior only for selected functions with
Unprotect[OptionValue];
OptionValue[f, opts_, "newopt"] := ...
OptionValue[h, opts_, "newopt"] := ...
Protect[OptionValue];
making this somewhat more robust. I also tried TagSet
f /: OptionValue[f, opts_, "newopt"] := ...
but this wouldn't work as OptionValue
still branches to its conventional definition this way.
This is my new approach. It is minimal invasive in the sense that it has to redefine OptionValue
to handle only the new option "newopt"
differently:
optionAliases = <|"newopt" -> "oldopt"|>;
Unprotect[OptionValue];
OptionValue[f_, opts_, "newopt"] := If[
("newopt" /. opts) =!= "newopt",
First[OptionValue[f, opts, "newopt"]],
First[OptionValue[f, opts, optionAliases["newopt"]]] /.
Automatic :> First[OptionValue[f, opts, "newopt"]]
];
Protect[OptionValue];
Now declare a function f
in the classical way, but set the defaults for all "old" options to Automatic
(or to another custom symbol):
ClearAll[f];
Options[f] =
"newopt" -> 1,
"oldopt" -> Automatic
;
f[opts : OptionsPattern] := OptionValue["newopt"]
Let's see what happens:
f
f["newopt" -> 2]
f["oldopt" -> 3]
f["newopt" -> 4, "oldopt" -> 3]
1
2
3
4
Note that we rely on the fact that OptionValue
will treat OptionValue[f, opts, "newopt"]
as before. So this will only work if you call OptionValue["newopt"]
, not if you request it with several option values at once like in OptionValue["opt1", "opt2", ... "newopt", ... ]
. One might be able to make it work by specifying an additional rule àla
OptionValue[
f_,
opts_,
list_List?(x [Function] Length[list] >= 2 && MemberQ[list, "newopt"])
] := ...
But I am afraid that this will slow down the overall system.
Of course, you can also specify special behavior only for selected functions with
Unprotect[OptionValue];
OptionValue[f, opts_, "newopt"] := ...
OptionValue[h, opts_, "newopt"] := ...
Protect[OptionValue];
making this somewhat more robust. I also tried TagSet
f /: OptionValue[f, opts_, "newopt"] := ...
but this wouldn't work as OptionValue
still branches to its conventional definition this way.
edited 5 mins ago
answered 38 mins ago


Henrik Schumacher
43.8k263129
43.8k263129
add a comment |Â
add a comment |Â
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fmathematica.stackexchange.com%2fquestions%2f185264%2frenaming-options-of-custom-functions-while-preserving-backwards-compatibility%23new-answer', 'question_page');
);
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password