Renaming options of custom functions while preserving backwards compatibility











up vote
12
down vote

favorite
1












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.










share|improve this question




























    up vote
    12
    down vote

    favorite
    1












    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.










    share|improve this question


























      up vote
      12
      down vote

      favorite
      1









      up vote
      12
      down vote

      favorite
      1






      1





      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.










      share|improve this question















      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.







      function-construction options guidelines






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited Nov 4 at 23:44









      Kuba

      102k11198502




      102k11198502










      asked Nov 4 at 13:24









      Szabolcs

      156k13423912




      156k13423912






















          4 Answers
          4






          active

          oldest

          votes

















          up vote
          7
          down vote



          accepted










          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.



          Alternative solution



          Note: This is heavily based on @Henrik Schumacher's answer, so be sure to upvote that one if this is useful



          Using the idea of adding special casing for certain symbols to OptionValue, we get the following solution:



          processing = False;
          AddLegacyOptionHandling[sym_] := (
          OptionValue[sym, opts_, names_] /; ! processing ^:= Block[
          {processing = True},
          OptionValue[sym, opts, names] //. Fallback[old_] :> OptionValue[sym, opts, old]
          ]
          )


          After calling AddLegacyOptionHandling[f], this works exactly as in the examples above.



          The following version also supports the fourth argument of OptionValue:



          processing = False;
          Attributes[OptionWrapper] = {Flat, HoldAll};
          AddLegacyOptionHandling[sym_] := (
          OptionValue[sym, opts_, names_, wrapper_ | PatternSequence] /; ! processing ^:= Block[
          {processing = True},
          OptionValue[sym, opts, names, OptionWrapper] //.
          Fallback[old_] :> With[
          {val = OptionValue[sym, opts, old, OptionWrapper]},
          val /; True
          ] /.
          Verbatim[OptionWrapper][val_] :> If[{wrapper} === {}, val, wrapper[val]]
          ]
          )


          The code is slightly more complex now as we need to be careful with evaluation leaks. But all in all, this version should support all forms of OptionValue, that is both lists of option names and Hold wrappers, while incurring a negligible performance hit for options that are not set to Fallback[…] and no impact for unaffected functions.






          share|improve this answer






























            up vote
            7
            down vote













            This may be more work than OP was asking to do, so in that respect it might not be an answer to the question. I think that if you are trying to do something that requires adding definitions to OptionValue or other such gymnastics, it's a good sign you should do something else.



            I would simply define a replacement rule and a message,



            General::mypackagedeprec = "Option name `1` is deprecated and will not be supported in future versions of MyPackage. Use `2` instead."

            fixOptionNames[func_] := ReplaceAll[
            HoldPattern[Rule["oldOptionName", rhs_]] :> (
            Message[
            MessageName[ func, "deprec"],
            "oldOptionName",
            "newOptionName"
            ];
            "newOptionName" -> rhs
            )
            ]


            And then I would replace all instances of



            OptionValue[ MyFunction, options, "oldOptionName"]


            with



            OptionValue[ MyFunction, fixOptionNames[MyFunction] @ options, "oldOptionName"]


            Or even better, if you use the paradigm of having MyFunction call Catch[ iMyFunction[...]] then you can perform the replacement at that point. It does involve manually going through and making adjustments, but that seems the price to pay for renaming an option.



            I like the message because it means you alert the users to the new name, and then after a few releases you can redefine fixOptionNames[___] to Identity or remove it entirely, but if you want to accept the old name in perpetuity then you'll need to keep it around.






            share|improve this answer























            • The problem is that I almost never use OptionValue[ MyFunction, options, "oldOptionName"]. I usually use OptionValue["someName"]. So far Lukas's solution looks best. As for redefining or modifying OptionValue: that is not something I am willing to do in a published package.
              – Szabolcs
              Nov 4 at 17:36












            • @Szabolcs Do you still find it problemating to add rules to OptionValue with TagSet? Technically that is not modifying OptionValue.
              – Henrik Schumacher
              Nov 4 at 19:27


















            up vote
            6
            down vote













            I have not heavily tested it but I decided to give it a try because I don't like minor issues with other proposals. E.g. that old options stay in Options or the way legacy rules are applied/used.



            Here it is:



            ClearAll[LegacyOptions]

            LegacyOptions[f_Symbol, rules : {__Rule}] := (
            f[a___, (r : Rule | RuleDelayed)[#, val_], b___] := f[a, r[#2, val], b]
            ) & @@@ rules


            So the problem can be that this DownValue gets below any DownValue with OptionsPattern and the the old option will throw an error.



            Here it is in action:



            foo // ClearAll;

            foo // Options = {
            "newName" -> 1,
            "newName2" :> 1 + 1
            };

            foo ~ LegacyOptions ~ {
            "oldName" -> "newName",
            "oldName2" -> "newName2"
            };

            foo[x_Integer, patt : OptionsPattern] := {
            x^2,
            OptionValue["newName"],
            OptionValue[Automatic, Automatic, "newName2", Hold]
            };





            foo[1, "oldName2" :> 1 + 2, "oldName" -> 4]



            {1, 4, Hold[1 + 2]}



            Let me know if you manage to break it.






            share|improve this answer

















            • 1




              foo[1, {"newName2" :> 1 + 2, "newName" -> 4}] works but foo[1, {"oldName2" :> 1 + 2, "oldName" -> 4}] does not.
              – Michael E2
              Nov 5 at 2:38










            • @MichaelE2 you mean w/wo {}? Yes, I was never a fan of OptionsPattern allowing them ;)
              – Kuba
              Nov 5 at 7:06










            • I guess they did it to make processing {opts} convenient since Sequence[opts] is such an oxidizing agent. I use it fairly often when, say, writing a solver that needs to to call NDSolve with some user-supplied options and FindRoot with others. I was thinking that a user of a package might want to do the same. I had an idea based on one of halirutan's. Don't know if it's any good though.
              – Michael E2
              Nov 5 at 12:13










            • @MichaelE2 yes, probably. Will try to think about a workaround for this issue.
              – Kuba
              Nov 5 at 12:45


















            up vote
            6
            down vote













            This is my new approach. It is minimally invasive in the sense that it has to redefine OptionValue to handle only the new option "newopt" for the function f differently.



            Now declare a function f in the classical way, but set the defaults for all "old" options to Automatic (or to any other custom symbol):



            Options[f] = {
            "newopt" -> 1,
            "oldopt" -> Automatic
            };
            f[opts : OptionsPattern] := OptionValue["newopt"]


            Add a new rule to OptionValue that is associated to f:



            optionAliases = <|"newopt" -> {"oldopt"}|>;

            f /: 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"}]]
            ];


            Now let's see what happens:



            f
            f["newopt" -> 2]
            f["oldopt" -> 3]
            f["newopt" -> 4, "oldopt" -> 3]
            f["oldopt" -> 3, "newopt" -> 5]



            1



            2



            3



            4



            5




            So, this always gives preference to values for "newopt" over values for "oldopt".



            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



            f /: OptionValue[
            f,
            opts_,
            list_List?(x [Function] Length[list] >= 2 && MemberQ[list, "newopt"])
            ] := ...





            share|improve this answer























            • Nice solution! Although I wonder whether the global nature of these rules incurs any performance hit... (but that should at least be minimized with the explicit listing of the functions handled this way). And one more note: For me, the TagSet version seems to work just fine - what exactly is the problem for you?
              – Lukas Lang
              Nov 4 at 16:15










            • Oh, it works for you? Ah, the ClearAll[f] cleared the TagSet definition (doh!). The better it is! This should make it really minimally invasive.
              – Henrik Schumacher
              Nov 4 at 16:48










            • I've added an alternative solution to my answer that was heavily inspired by yours, I hope that's ok (otherwise, feel free to move that part to your answer)
              – Lukas Lang
              Nov 4 at 17:17










            • With some variations of this, it is not actually necessary to add the old option to the Options list of the symbol. That means that (other than the renaming), the modifications necessary for backwards compatibility can be kept isolated, and easily removed at some later time. The compromise is that usage with SetOptions is not backwards compatible. But this is rare.
              – Szabolcs
              Nov 5 at 9:16










            • Hm. I added the old option to the options list because Mathematic would complain about "oldopt" not being a valid option for f...
              – Henrik Schumacher
              Nov 5 at 9:19











            Your Answer





            StackExchange.ifUsing("editor", function () {
            return StackExchange.using("mathjaxEditing", function () {
            StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
            StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["$", "$"], ["\\(","\\)"]]);
            });
            });
            }, "mathjax-editing");

            StackExchange.ready(function() {
            var channelOptions = {
            tags: "".split(" "),
            id: "387"
            };
            initTagRenderer("".split(" "), "".split(" "), channelOptions);

            StackExchange.using("externalEditor", function() {
            // Have to fire editor after snippets, if snippets enabled
            if (StackExchange.settings.snippets.snippetsEnabled) {
            StackExchange.using("snippets", function() {
            createEditor();
            });
            }
            else {
            createEditor();
            }
            });

            function createEditor() {
            StackExchange.prepareEditor({
            heartbeatType: 'answer',
            convertImagesToLinks: false,
            noModals: true,
            showLowRepImageUploadWarning: true,
            reputationToPostImages: null,
            bindNavPrevention: true,
            postfix: "",
            imageUploader: {
            brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
            contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
            allowUrls: true
            },
            onDemand: true,
            discardSelector: ".discard-answer"
            ,immediatelyShowMarkdownHelp:true
            });


            }
            });














             

            draft saved


            draft discarded


















            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
































            4 Answers
            4






            active

            oldest

            votes








            4 Answers
            4






            active

            oldest

            votes









            active

            oldest

            votes






            active

            oldest

            votes








            up vote
            7
            down vote



            accepted










            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.



            Alternative solution



            Note: This is heavily based on @Henrik Schumacher's answer, so be sure to upvote that one if this is useful



            Using the idea of adding special casing for certain symbols to OptionValue, we get the following solution:



            processing = False;
            AddLegacyOptionHandling[sym_] := (
            OptionValue[sym, opts_, names_] /; ! processing ^:= Block[
            {processing = True},
            OptionValue[sym, opts, names] //. Fallback[old_] :> OptionValue[sym, opts, old]
            ]
            )


            After calling AddLegacyOptionHandling[f], this works exactly as in the examples above.



            The following version also supports the fourth argument of OptionValue:



            processing = False;
            Attributes[OptionWrapper] = {Flat, HoldAll};
            AddLegacyOptionHandling[sym_] := (
            OptionValue[sym, opts_, names_, wrapper_ | PatternSequence] /; ! processing ^:= Block[
            {processing = True},
            OptionValue[sym, opts, names, OptionWrapper] //.
            Fallback[old_] :> With[
            {val = OptionValue[sym, opts, old, OptionWrapper]},
            val /; True
            ] /.
            Verbatim[OptionWrapper][val_] :> If[{wrapper} === {}, val, wrapper[val]]
            ]
            )


            The code is slightly more complex now as we need to be careful with evaluation leaks. But all in all, this version should support all forms of OptionValue, that is both lists of option names and Hold wrappers, while incurring a negligible performance hit for options that are not set to Fallback[…] and no impact for unaffected functions.






            share|improve this answer



























              up vote
              7
              down vote



              accepted










              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.



              Alternative solution



              Note: This is heavily based on @Henrik Schumacher's answer, so be sure to upvote that one if this is useful



              Using the idea of adding special casing for certain symbols to OptionValue, we get the following solution:



              processing = False;
              AddLegacyOptionHandling[sym_] := (
              OptionValue[sym, opts_, names_] /; ! processing ^:= Block[
              {processing = True},
              OptionValue[sym, opts, names] //. Fallback[old_] :> OptionValue[sym, opts, old]
              ]
              )


              After calling AddLegacyOptionHandling[f], this works exactly as in the examples above.



              The following version also supports the fourth argument of OptionValue:



              processing = False;
              Attributes[OptionWrapper] = {Flat, HoldAll};
              AddLegacyOptionHandling[sym_] := (
              OptionValue[sym, opts_, names_, wrapper_ | PatternSequence] /; ! processing ^:= Block[
              {processing = True},
              OptionValue[sym, opts, names, OptionWrapper] //.
              Fallback[old_] :> With[
              {val = OptionValue[sym, opts, old, OptionWrapper]},
              val /; True
              ] /.
              Verbatim[OptionWrapper][val_] :> If[{wrapper} === {}, val, wrapper[val]]
              ]
              )


              The code is slightly more complex now as we need to be careful with evaluation leaks. But all in all, this version should support all forms of OptionValue, that is both lists of option names and Hold wrappers, while incurring a negligible performance hit for options that are not set to Fallback[…] and no impact for unaffected functions.






              share|improve this answer

























                up vote
                7
                down vote



                accepted







                up vote
                7
                down vote



                accepted






                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.



                Alternative solution



                Note: This is heavily based on @Henrik Schumacher's answer, so be sure to upvote that one if this is useful



                Using the idea of adding special casing for certain symbols to OptionValue, we get the following solution:



                processing = False;
                AddLegacyOptionHandling[sym_] := (
                OptionValue[sym, opts_, names_] /; ! processing ^:= Block[
                {processing = True},
                OptionValue[sym, opts, names] //. Fallback[old_] :> OptionValue[sym, opts, old]
                ]
                )


                After calling AddLegacyOptionHandling[f], this works exactly as in the examples above.



                The following version also supports the fourth argument of OptionValue:



                processing = False;
                Attributes[OptionWrapper] = {Flat, HoldAll};
                AddLegacyOptionHandling[sym_] := (
                OptionValue[sym, opts_, names_, wrapper_ | PatternSequence] /; ! processing ^:= Block[
                {processing = True},
                OptionValue[sym, opts, names, OptionWrapper] //.
                Fallback[old_] :> With[
                {val = OptionValue[sym, opts, old, OptionWrapper]},
                val /; True
                ] /.
                Verbatim[OptionWrapper][val_] :> If[{wrapper} === {}, val, wrapper[val]]
                ]
                )


                The code is slightly more complex now as we need to be careful with evaluation leaks. But all in all, this version should support all forms of OptionValue, that is both lists of option names and Hold wrappers, while incurring a negligible performance hit for options that are not set to Fallback[…] and no impact for unaffected functions.






                share|improve this answer














                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.



                Alternative solution



                Note: This is heavily based on @Henrik Schumacher's answer, so be sure to upvote that one if this is useful



                Using the idea of adding special casing for certain symbols to OptionValue, we get the following solution:



                processing = False;
                AddLegacyOptionHandling[sym_] := (
                OptionValue[sym, opts_, names_] /; ! processing ^:= Block[
                {processing = True},
                OptionValue[sym, opts, names] //. Fallback[old_] :> OptionValue[sym, opts, old]
                ]
                )


                After calling AddLegacyOptionHandling[f], this works exactly as in the examples above.



                The following version also supports the fourth argument of OptionValue:



                processing = False;
                Attributes[OptionWrapper] = {Flat, HoldAll};
                AddLegacyOptionHandling[sym_] := (
                OptionValue[sym, opts_, names_, wrapper_ | PatternSequence] /; ! processing ^:= Block[
                {processing = True},
                OptionValue[sym, opts, names, OptionWrapper] //.
                Fallback[old_] :> With[
                {val = OptionValue[sym, opts, old, OptionWrapper]},
                val /; True
                ] /.
                Verbatim[OptionWrapper][val_] :> If[{wrapper} === {}, val, wrapper[val]]
                ]
                )


                The code is slightly more complex now as we need to be careful with evaluation leaks. But all in all, this version should support all forms of OptionValue, that is both lists of option names and Hold wrappers, while incurring a negligible performance hit for options that are not set to Fallback[…] and no impact for unaffected functions.







                share|improve this answer














                share|improve this answer



                share|improve this answer








                edited Nov 4 at 17:31

























                answered Nov 4 at 13:52









                Lukas Lang

                5,4731525




                5,4731525






















                    up vote
                    7
                    down vote













                    This may be more work than OP was asking to do, so in that respect it might not be an answer to the question. I think that if you are trying to do something that requires adding definitions to OptionValue or other such gymnastics, it's a good sign you should do something else.



                    I would simply define a replacement rule and a message,



                    General::mypackagedeprec = "Option name `1` is deprecated and will not be supported in future versions of MyPackage. Use `2` instead."

                    fixOptionNames[func_] := ReplaceAll[
                    HoldPattern[Rule["oldOptionName", rhs_]] :> (
                    Message[
                    MessageName[ func, "deprec"],
                    "oldOptionName",
                    "newOptionName"
                    ];
                    "newOptionName" -> rhs
                    )
                    ]


                    And then I would replace all instances of



                    OptionValue[ MyFunction, options, "oldOptionName"]


                    with



                    OptionValue[ MyFunction, fixOptionNames[MyFunction] @ options, "oldOptionName"]


                    Or even better, if you use the paradigm of having MyFunction call Catch[ iMyFunction[...]] then you can perform the replacement at that point. It does involve manually going through and making adjustments, but that seems the price to pay for renaming an option.



                    I like the message because it means you alert the users to the new name, and then after a few releases you can redefine fixOptionNames[___] to Identity or remove it entirely, but if you want to accept the old name in perpetuity then you'll need to keep it around.






                    share|improve this answer























                    • The problem is that I almost never use OptionValue[ MyFunction, options, "oldOptionName"]. I usually use OptionValue["someName"]. So far Lukas's solution looks best. As for redefining or modifying OptionValue: that is not something I am willing to do in a published package.
                      – Szabolcs
                      Nov 4 at 17:36












                    • @Szabolcs Do you still find it problemating to add rules to OptionValue with TagSet? Technically that is not modifying OptionValue.
                      – Henrik Schumacher
                      Nov 4 at 19:27















                    up vote
                    7
                    down vote













                    This may be more work than OP was asking to do, so in that respect it might not be an answer to the question. I think that if you are trying to do something that requires adding definitions to OptionValue or other such gymnastics, it's a good sign you should do something else.



                    I would simply define a replacement rule and a message,



                    General::mypackagedeprec = "Option name `1` is deprecated and will not be supported in future versions of MyPackage. Use `2` instead."

                    fixOptionNames[func_] := ReplaceAll[
                    HoldPattern[Rule["oldOptionName", rhs_]] :> (
                    Message[
                    MessageName[ func, "deprec"],
                    "oldOptionName",
                    "newOptionName"
                    ];
                    "newOptionName" -> rhs
                    )
                    ]


                    And then I would replace all instances of



                    OptionValue[ MyFunction, options, "oldOptionName"]


                    with



                    OptionValue[ MyFunction, fixOptionNames[MyFunction] @ options, "oldOptionName"]


                    Or even better, if you use the paradigm of having MyFunction call Catch[ iMyFunction[...]] then you can perform the replacement at that point. It does involve manually going through and making adjustments, but that seems the price to pay for renaming an option.



                    I like the message because it means you alert the users to the new name, and then after a few releases you can redefine fixOptionNames[___] to Identity or remove it entirely, but if you want to accept the old name in perpetuity then you'll need to keep it around.






                    share|improve this answer























                    • The problem is that I almost never use OptionValue[ MyFunction, options, "oldOptionName"]. I usually use OptionValue["someName"]. So far Lukas's solution looks best. As for redefining or modifying OptionValue: that is not something I am willing to do in a published package.
                      – Szabolcs
                      Nov 4 at 17:36












                    • @Szabolcs Do you still find it problemating to add rules to OptionValue with TagSet? Technically that is not modifying OptionValue.
                      – Henrik Schumacher
                      Nov 4 at 19:27













                    up vote
                    7
                    down vote










                    up vote
                    7
                    down vote









                    This may be more work than OP was asking to do, so in that respect it might not be an answer to the question. I think that if you are trying to do something that requires adding definitions to OptionValue or other such gymnastics, it's a good sign you should do something else.



                    I would simply define a replacement rule and a message,



                    General::mypackagedeprec = "Option name `1` is deprecated and will not be supported in future versions of MyPackage. Use `2` instead."

                    fixOptionNames[func_] := ReplaceAll[
                    HoldPattern[Rule["oldOptionName", rhs_]] :> (
                    Message[
                    MessageName[ func, "deprec"],
                    "oldOptionName",
                    "newOptionName"
                    ];
                    "newOptionName" -> rhs
                    )
                    ]


                    And then I would replace all instances of



                    OptionValue[ MyFunction, options, "oldOptionName"]


                    with



                    OptionValue[ MyFunction, fixOptionNames[MyFunction] @ options, "oldOptionName"]


                    Or even better, if you use the paradigm of having MyFunction call Catch[ iMyFunction[...]] then you can perform the replacement at that point. It does involve manually going through and making adjustments, but that seems the price to pay for renaming an option.



                    I like the message because it means you alert the users to the new name, and then after a few releases you can redefine fixOptionNames[___] to Identity or remove it entirely, but if you want to accept the old name in perpetuity then you'll need to keep it around.






                    share|improve this answer














                    This may be more work than OP was asking to do, so in that respect it might not be an answer to the question. I think that if you are trying to do something that requires adding definitions to OptionValue or other such gymnastics, it's a good sign you should do something else.



                    I would simply define a replacement rule and a message,



                    General::mypackagedeprec = "Option name `1` is deprecated and will not be supported in future versions of MyPackage. Use `2` instead."

                    fixOptionNames[func_] := ReplaceAll[
                    HoldPattern[Rule["oldOptionName", rhs_]] :> (
                    Message[
                    MessageName[ func, "deprec"],
                    "oldOptionName",
                    "newOptionName"
                    ];
                    "newOptionName" -> rhs
                    )
                    ]


                    And then I would replace all instances of



                    OptionValue[ MyFunction, options, "oldOptionName"]


                    with



                    OptionValue[ MyFunction, fixOptionNames[MyFunction] @ options, "oldOptionName"]


                    Or even better, if you use the paradigm of having MyFunction call Catch[ iMyFunction[...]] then you can perform the replacement at that point. It does involve manually going through and making adjustments, but that seems the price to pay for renaming an option.



                    I like the message because it means you alert the users to the new name, and then after a few releases you can redefine fixOptionNames[___] to Identity or remove it entirely, but if you want to accept the old name in perpetuity then you'll need to keep it around.







                    share|improve this answer














                    share|improve this answer



                    share|improve this answer








                    edited Nov 4 at 17:13

























                    answered Nov 4 at 17:04









                    Jason B.

                    47k385183




                    47k385183












                    • The problem is that I almost never use OptionValue[ MyFunction, options, "oldOptionName"]. I usually use OptionValue["someName"]. So far Lukas's solution looks best. As for redefining or modifying OptionValue: that is not something I am willing to do in a published package.
                      – Szabolcs
                      Nov 4 at 17:36












                    • @Szabolcs Do you still find it problemating to add rules to OptionValue with TagSet? Technically that is not modifying OptionValue.
                      – Henrik Schumacher
                      Nov 4 at 19:27


















                    • The problem is that I almost never use OptionValue[ MyFunction, options, "oldOptionName"]. I usually use OptionValue["someName"]. So far Lukas's solution looks best. As for redefining or modifying OptionValue: that is not something I am willing to do in a published package.
                      – Szabolcs
                      Nov 4 at 17:36












                    • @Szabolcs Do you still find it problemating to add rules to OptionValue with TagSet? Technically that is not modifying OptionValue.
                      – Henrik Schumacher
                      Nov 4 at 19:27
















                    The problem is that I almost never use OptionValue[ MyFunction, options, "oldOptionName"]. I usually use OptionValue["someName"]. So far Lukas's solution looks best. As for redefining or modifying OptionValue: that is not something I am willing to do in a published package.
                    – Szabolcs
                    Nov 4 at 17:36






                    The problem is that I almost never use OptionValue[ MyFunction, options, "oldOptionName"]. I usually use OptionValue["someName"]. So far Lukas's solution looks best. As for redefining or modifying OptionValue: that is not something I am willing to do in a published package.
                    – Szabolcs
                    Nov 4 at 17:36














                    @Szabolcs Do you still find it problemating to add rules to OptionValue with TagSet? Technically that is not modifying OptionValue.
                    – Henrik Schumacher
                    Nov 4 at 19:27




                    @Szabolcs Do you still find it problemating to add rules to OptionValue with TagSet? Technically that is not modifying OptionValue.
                    – Henrik Schumacher
                    Nov 4 at 19:27










                    up vote
                    6
                    down vote













                    I have not heavily tested it but I decided to give it a try because I don't like minor issues with other proposals. E.g. that old options stay in Options or the way legacy rules are applied/used.



                    Here it is:



                    ClearAll[LegacyOptions]

                    LegacyOptions[f_Symbol, rules : {__Rule}] := (
                    f[a___, (r : Rule | RuleDelayed)[#, val_], b___] := f[a, r[#2, val], b]
                    ) & @@@ rules


                    So the problem can be that this DownValue gets below any DownValue with OptionsPattern and the the old option will throw an error.



                    Here it is in action:



                    foo // ClearAll;

                    foo // Options = {
                    "newName" -> 1,
                    "newName2" :> 1 + 1
                    };

                    foo ~ LegacyOptions ~ {
                    "oldName" -> "newName",
                    "oldName2" -> "newName2"
                    };

                    foo[x_Integer, patt : OptionsPattern] := {
                    x^2,
                    OptionValue["newName"],
                    OptionValue[Automatic, Automatic, "newName2", Hold]
                    };





                    foo[1, "oldName2" :> 1 + 2, "oldName" -> 4]



                    {1, 4, Hold[1 + 2]}



                    Let me know if you manage to break it.






                    share|improve this answer

















                    • 1




                      foo[1, {"newName2" :> 1 + 2, "newName" -> 4}] works but foo[1, {"oldName2" :> 1 + 2, "oldName" -> 4}] does not.
                      – Michael E2
                      Nov 5 at 2:38










                    • @MichaelE2 you mean w/wo {}? Yes, I was never a fan of OptionsPattern allowing them ;)
                      – Kuba
                      Nov 5 at 7:06










                    • I guess they did it to make processing {opts} convenient since Sequence[opts] is such an oxidizing agent. I use it fairly often when, say, writing a solver that needs to to call NDSolve with some user-supplied options and FindRoot with others. I was thinking that a user of a package might want to do the same. I had an idea based on one of halirutan's. Don't know if it's any good though.
                      – Michael E2
                      Nov 5 at 12:13










                    • @MichaelE2 yes, probably. Will try to think about a workaround for this issue.
                      – Kuba
                      Nov 5 at 12:45















                    up vote
                    6
                    down vote













                    I have not heavily tested it but I decided to give it a try because I don't like minor issues with other proposals. E.g. that old options stay in Options or the way legacy rules are applied/used.



                    Here it is:



                    ClearAll[LegacyOptions]

                    LegacyOptions[f_Symbol, rules : {__Rule}] := (
                    f[a___, (r : Rule | RuleDelayed)[#, val_], b___] := f[a, r[#2, val], b]
                    ) & @@@ rules


                    So the problem can be that this DownValue gets below any DownValue with OptionsPattern and the the old option will throw an error.



                    Here it is in action:



                    foo // ClearAll;

                    foo // Options = {
                    "newName" -> 1,
                    "newName2" :> 1 + 1
                    };

                    foo ~ LegacyOptions ~ {
                    "oldName" -> "newName",
                    "oldName2" -> "newName2"
                    };

                    foo[x_Integer, patt : OptionsPattern] := {
                    x^2,
                    OptionValue["newName"],
                    OptionValue[Automatic, Automatic, "newName2", Hold]
                    };





                    foo[1, "oldName2" :> 1 + 2, "oldName" -> 4]



                    {1, 4, Hold[1 + 2]}



                    Let me know if you manage to break it.






                    share|improve this answer

















                    • 1




                      foo[1, {"newName2" :> 1 + 2, "newName" -> 4}] works but foo[1, {"oldName2" :> 1 + 2, "oldName" -> 4}] does not.
                      – Michael E2
                      Nov 5 at 2:38










                    • @MichaelE2 you mean w/wo {}? Yes, I was never a fan of OptionsPattern allowing them ;)
                      – Kuba
                      Nov 5 at 7:06










                    • I guess they did it to make processing {opts} convenient since Sequence[opts] is such an oxidizing agent. I use it fairly often when, say, writing a solver that needs to to call NDSolve with some user-supplied options and FindRoot with others. I was thinking that a user of a package might want to do the same. I had an idea based on one of halirutan's. Don't know if it's any good though.
                      – Michael E2
                      Nov 5 at 12:13










                    • @MichaelE2 yes, probably. Will try to think about a workaround for this issue.
                      – Kuba
                      Nov 5 at 12:45













                    up vote
                    6
                    down vote










                    up vote
                    6
                    down vote









                    I have not heavily tested it but I decided to give it a try because I don't like minor issues with other proposals. E.g. that old options stay in Options or the way legacy rules are applied/used.



                    Here it is:



                    ClearAll[LegacyOptions]

                    LegacyOptions[f_Symbol, rules : {__Rule}] := (
                    f[a___, (r : Rule | RuleDelayed)[#, val_], b___] := f[a, r[#2, val], b]
                    ) & @@@ rules


                    So the problem can be that this DownValue gets below any DownValue with OptionsPattern and the the old option will throw an error.



                    Here it is in action:



                    foo // ClearAll;

                    foo // Options = {
                    "newName" -> 1,
                    "newName2" :> 1 + 1
                    };

                    foo ~ LegacyOptions ~ {
                    "oldName" -> "newName",
                    "oldName2" -> "newName2"
                    };

                    foo[x_Integer, patt : OptionsPattern] := {
                    x^2,
                    OptionValue["newName"],
                    OptionValue[Automatic, Automatic, "newName2", Hold]
                    };





                    foo[1, "oldName2" :> 1 + 2, "oldName" -> 4]



                    {1, 4, Hold[1 + 2]}



                    Let me know if you manage to break it.






                    share|improve this answer












                    I have not heavily tested it but I decided to give it a try because I don't like minor issues with other proposals. E.g. that old options stay in Options or the way legacy rules are applied/used.



                    Here it is:



                    ClearAll[LegacyOptions]

                    LegacyOptions[f_Symbol, rules : {__Rule}] := (
                    f[a___, (r : Rule | RuleDelayed)[#, val_], b___] := f[a, r[#2, val], b]
                    ) & @@@ rules


                    So the problem can be that this DownValue gets below any DownValue with OptionsPattern and the the old option will throw an error.



                    Here it is in action:



                    foo // ClearAll;

                    foo // Options = {
                    "newName" -> 1,
                    "newName2" :> 1 + 1
                    };

                    foo ~ LegacyOptions ~ {
                    "oldName" -> "newName",
                    "oldName2" -> "newName2"
                    };

                    foo[x_Integer, patt : OptionsPattern] := {
                    x^2,
                    OptionValue["newName"],
                    OptionValue[Automatic, Automatic, "newName2", Hold]
                    };





                    foo[1, "oldName2" :> 1 + 2, "oldName" -> 4]



                    {1, 4, Hold[1 + 2]}



                    Let me know if you manage to break it.







                    share|improve this answer












                    share|improve this answer



                    share|improve this answer










                    answered Nov 4 at 19:11









                    Kuba

                    102k11198502




                    102k11198502








                    • 1




                      foo[1, {"newName2" :> 1 + 2, "newName" -> 4}] works but foo[1, {"oldName2" :> 1 + 2, "oldName" -> 4}] does not.
                      – Michael E2
                      Nov 5 at 2:38










                    • @MichaelE2 you mean w/wo {}? Yes, I was never a fan of OptionsPattern allowing them ;)
                      – Kuba
                      Nov 5 at 7:06










                    • I guess they did it to make processing {opts} convenient since Sequence[opts] is such an oxidizing agent. I use it fairly often when, say, writing a solver that needs to to call NDSolve with some user-supplied options and FindRoot with others. I was thinking that a user of a package might want to do the same. I had an idea based on one of halirutan's. Don't know if it's any good though.
                      – Michael E2
                      Nov 5 at 12:13










                    • @MichaelE2 yes, probably. Will try to think about a workaround for this issue.
                      – Kuba
                      Nov 5 at 12:45














                    • 1




                      foo[1, {"newName2" :> 1 + 2, "newName" -> 4}] works but foo[1, {"oldName2" :> 1 + 2, "oldName" -> 4}] does not.
                      – Michael E2
                      Nov 5 at 2:38










                    • @MichaelE2 you mean w/wo {}? Yes, I was never a fan of OptionsPattern allowing them ;)
                      – Kuba
                      Nov 5 at 7:06










                    • I guess they did it to make processing {opts} convenient since Sequence[opts] is such an oxidizing agent. I use it fairly often when, say, writing a solver that needs to to call NDSolve with some user-supplied options and FindRoot with others. I was thinking that a user of a package might want to do the same. I had an idea based on one of halirutan's. Don't know if it's any good though.
                      – Michael E2
                      Nov 5 at 12:13










                    • @MichaelE2 yes, probably. Will try to think about a workaround for this issue.
                      – Kuba
                      Nov 5 at 12:45








                    1




                    1




                    foo[1, {"newName2" :> 1 + 2, "newName" -> 4}] works but foo[1, {"oldName2" :> 1 + 2, "oldName" -> 4}] does not.
                    – Michael E2
                    Nov 5 at 2:38




                    foo[1, {"newName2" :> 1 + 2, "newName" -> 4}] works but foo[1, {"oldName2" :> 1 + 2, "oldName" -> 4}] does not.
                    – Michael E2
                    Nov 5 at 2:38












                    @MichaelE2 you mean w/wo {}? Yes, I was never a fan of OptionsPattern allowing them ;)
                    – Kuba
                    Nov 5 at 7:06




                    @MichaelE2 you mean w/wo {}? Yes, I was never a fan of OptionsPattern allowing them ;)
                    – Kuba
                    Nov 5 at 7:06












                    I guess they did it to make processing {opts} convenient since Sequence[opts] is such an oxidizing agent. I use it fairly often when, say, writing a solver that needs to to call NDSolve with some user-supplied options and FindRoot with others. I was thinking that a user of a package might want to do the same. I had an idea based on one of halirutan's. Don't know if it's any good though.
                    – Michael E2
                    Nov 5 at 12:13




                    I guess they did it to make processing {opts} convenient since Sequence[opts] is such an oxidizing agent. I use it fairly often when, say, writing a solver that needs to to call NDSolve with some user-supplied options and FindRoot with others. I was thinking that a user of a package might want to do the same. I had an idea based on one of halirutan's. Don't know if it's any good though.
                    – Michael E2
                    Nov 5 at 12:13












                    @MichaelE2 yes, probably. Will try to think about a workaround for this issue.
                    – Kuba
                    Nov 5 at 12:45




                    @MichaelE2 yes, probably. Will try to think about a workaround for this issue.
                    – Kuba
                    Nov 5 at 12:45










                    up vote
                    6
                    down vote













                    This is my new approach. It is minimally invasive in the sense that it has to redefine OptionValue to handle only the new option "newopt" for the function f differently.



                    Now declare a function f in the classical way, but set the defaults for all "old" options to Automatic (or to any other custom symbol):



                    Options[f] = {
                    "newopt" -> 1,
                    "oldopt" -> Automatic
                    };
                    f[opts : OptionsPattern] := OptionValue["newopt"]


                    Add a new rule to OptionValue that is associated to f:



                    optionAliases = <|"newopt" -> {"oldopt"}|>;

                    f /: 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"}]]
                    ];


                    Now let's see what happens:



                    f
                    f["newopt" -> 2]
                    f["oldopt" -> 3]
                    f["newopt" -> 4, "oldopt" -> 3]
                    f["oldopt" -> 3, "newopt" -> 5]



                    1



                    2



                    3



                    4



                    5




                    So, this always gives preference to values for "newopt" over values for "oldopt".



                    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



                    f /: OptionValue[
                    f,
                    opts_,
                    list_List?(x [Function] Length[list] >= 2 && MemberQ[list, "newopt"])
                    ] := ...





                    share|improve this answer























                    • Nice solution! Although I wonder whether the global nature of these rules incurs any performance hit... (but that should at least be minimized with the explicit listing of the functions handled this way). And one more note: For me, the TagSet version seems to work just fine - what exactly is the problem for you?
                      – Lukas Lang
                      Nov 4 at 16:15










                    • Oh, it works for you? Ah, the ClearAll[f] cleared the TagSet definition (doh!). The better it is! This should make it really minimally invasive.
                      – Henrik Schumacher
                      Nov 4 at 16:48










                    • I've added an alternative solution to my answer that was heavily inspired by yours, I hope that's ok (otherwise, feel free to move that part to your answer)
                      – Lukas Lang
                      Nov 4 at 17:17










                    • With some variations of this, it is not actually necessary to add the old option to the Options list of the symbol. That means that (other than the renaming), the modifications necessary for backwards compatibility can be kept isolated, and easily removed at some later time. The compromise is that usage with SetOptions is not backwards compatible. But this is rare.
                      – Szabolcs
                      Nov 5 at 9:16










                    • Hm. I added the old option to the options list because Mathematic would complain about "oldopt" not being a valid option for f...
                      – Henrik Schumacher
                      Nov 5 at 9:19















                    up vote
                    6
                    down vote













                    This is my new approach. It is minimally invasive in the sense that it has to redefine OptionValue to handle only the new option "newopt" for the function f differently.



                    Now declare a function f in the classical way, but set the defaults for all "old" options to Automatic (or to any other custom symbol):



                    Options[f] = {
                    "newopt" -> 1,
                    "oldopt" -> Automatic
                    };
                    f[opts : OptionsPattern] := OptionValue["newopt"]


                    Add a new rule to OptionValue that is associated to f:



                    optionAliases = <|"newopt" -> {"oldopt"}|>;

                    f /: 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"}]]
                    ];


                    Now let's see what happens:



                    f
                    f["newopt" -> 2]
                    f["oldopt" -> 3]
                    f["newopt" -> 4, "oldopt" -> 3]
                    f["oldopt" -> 3, "newopt" -> 5]



                    1



                    2



                    3



                    4



                    5




                    So, this always gives preference to values for "newopt" over values for "oldopt".



                    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



                    f /: OptionValue[
                    f,
                    opts_,
                    list_List?(x [Function] Length[list] >= 2 && MemberQ[list, "newopt"])
                    ] := ...





                    share|improve this answer























                    • Nice solution! Although I wonder whether the global nature of these rules incurs any performance hit... (but that should at least be minimized with the explicit listing of the functions handled this way). And one more note: For me, the TagSet version seems to work just fine - what exactly is the problem for you?
                      – Lukas Lang
                      Nov 4 at 16:15










                    • Oh, it works for you? Ah, the ClearAll[f] cleared the TagSet definition (doh!). The better it is! This should make it really minimally invasive.
                      – Henrik Schumacher
                      Nov 4 at 16:48










                    • I've added an alternative solution to my answer that was heavily inspired by yours, I hope that's ok (otherwise, feel free to move that part to your answer)
                      – Lukas Lang
                      Nov 4 at 17:17










                    • With some variations of this, it is not actually necessary to add the old option to the Options list of the symbol. That means that (other than the renaming), the modifications necessary for backwards compatibility can be kept isolated, and easily removed at some later time. The compromise is that usage with SetOptions is not backwards compatible. But this is rare.
                      – Szabolcs
                      Nov 5 at 9:16










                    • Hm. I added the old option to the options list because Mathematic would complain about "oldopt" not being a valid option for f...
                      – Henrik Schumacher
                      Nov 5 at 9:19













                    up vote
                    6
                    down vote










                    up vote
                    6
                    down vote









                    This is my new approach. It is minimally invasive in the sense that it has to redefine OptionValue to handle only the new option "newopt" for the function f differently.



                    Now declare a function f in the classical way, but set the defaults for all "old" options to Automatic (or to any other custom symbol):



                    Options[f] = {
                    "newopt" -> 1,
                    "oldopt" -> Automatic
                    };
                    f[opts : OptionsPattern] := OptionValue["newopt"]


                    Add a new rule to OptionValue that is associated to f:



                    optionAliases = <|"newopt" -> {"oldopt"}|>;

                    f /: 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"}]]
                    ];


                    Now let's see what happens:



                    f
                    f["newopt" -> 2]
                    f["oldopt" -> 3]
                    f["newopt" -> 4, "oldopt" -> 3]
                    f["oldopt" -> 3, "newopt" -> 5]



                    1



                    2



                    3



                    4



                    5




                    So, this always gives preference to values for "newopt" over values for "oldopt".



                    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



                    f /: OptionValue[
                    f,
                    opts_,
                    list_List?(x [Function] Length[list] >= 2 && MemberQ[list, "newopt"])
                    ] := ...





                    share|improve this answer














                    This is my new approach. It is minimally invasive in the sense that it has to redefine OptionValue to handle only the new option "newopt" for the function f differently.



                    Now declare a function f in the classical way, but set the defaults for all "old" options to Automatic (or to any other custom symbol):



                    Options[f] = {
                    "newopt" -> 1,
                    "oldopt" -> Automatic
                    };
                    f[opts : OptionsPattern] := OptionValue["newopt"]


                    Add a new rule to OptionValue that is associated to f:



                    optionAliases = <|"newopt" -> {"oldopt"}|>;

                    f /: 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"}]]
                    ];


                    Now let's see what happens:



                    f
                    f["newopt" -> 2]
                    f["oldopt" -> 3]
                    f["newopt" -> 4, "oldopt" -> 3]
                    f["oldopt" -> 3, "newopt" -> 5]



                    1



                    2



                    3



                    4



                    5




                    So, this always gives preference to values for "newopt" over values for "oldopt".



                    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



                    f /: OptionValue[
                    f,
                    opts_,
                    list_List?(x [Function] Length[list] >= 2 && MemberQ[list, "newopt"])
                    ] := ...






                    share|improve this answer














                    share|improve this answer



                    share|improve this answer








                    edited Nov 4 at 19:26

























                    answered Nov 4 at 14:10









                    Henrik Schumacher

                    44.2k263129




                    44.2k263129












                    • Nice solution! Although I wonder whether the global nature of these rules incurs any performance hit... (but that should at least be minimized with the explicit listing of the functions handled this way). And one more note: For me, the TagSet version seems to work just fine - what exactly is the problem for you?
                      – Lukas Lang
                      Nov 4 at 16:15










                    • Oh, it works for you? Ah, the ClearAll[f] cleared the TagSet definition (doh!). The better it is! This should make it really minimally invasive.
                      – Henrik Schumacher
                      Nov 4 at 16:48










                    • I've added an alternative solution to my answer that was heavily inspired by yours, I hope that's ok (otherwise, feel free to move that part to your answer)
                      – Lukas Lang
                      Nov 4 at 17:17










                    • With some variations of this, it is not actually necessary to add the old option to the Options list of the symbol. That means that (other than the renaming), the modifications necessary for backwards compatibility can be kept isolated, and easily removed at some later time. The compromise is that usage with SetOptions is not backwards compatible. But this is rare.
                      – Szabolcs
                      Nov 5 at 9:16










                    • Hm. I added the old option to the options list because Mathematic would complain about "oldopt" not being a valid option for f...
                      – Henrik Schumacher
                      Nov 5 at 9:19


















                    • Nice solution! Although I wonder whether the global nature of these rules incurs any performance hit... (but that should at least be minimized with the explicit listing of the functions handled this way). And one more note: For me, the TagSet version seems to work just fine - what exactly is the problem for you?
                      – Lukas Lang
                      Nov 4 at 16:15










                    • Oh, it works for you? Ah, the ClearAll[f] cleared the TagSet definition (doh!). The better it is! This should make it really minimally invasive.
                      – Henrik Schumacher
                      Nov 4 at 16:48










                    • I've added an alternative solution to my answer that was heavily inspired by yours, I hope that's ok (otherwise, feel free to move that part to your answer)
                      – Lukas Lang
                      Nov 4 at 17:17










                    • With some variations of this, it is not actually necessary to add the old option to the Options list of the symbol. That means that (other than the renaming), the modifications necessary for backwards compatibility can be kept isolated, and easily removed at some later time. The compromise is that usage with SetOptions is not backwards compatible. But this is rare.
                      – Szabolcs
                      Nov 5 at 9:16










                    • Hm. I added the old option to the options list because Mathematic would complain about "oldopt" not being a valid option for f...
                      – Henrik Schumacher
                      Nov 5 at 9:19
















                    Nice solution! Although I wonder whether the global nature of these rules incurs any performance hit... (but that should at least be minimized with the explicit listing of the functions handled this way). And one more note: For me, the TagSet version seems to work just fine - what exactly is the problem for you?
                    – Lukas Lang
                    Nov 4 at 16:15




                    Nice solution! Although I wonder whether the global nature of these rules incurs any performance hit... (but that should at least be minimized with the explicit listing of the functions handled this way). And one more note: For me, the TagSet version seems to work just fine - what exactly is the problem for you?
                    – Lukas Lang
                    Nov 4 at 16:15












                    Oh, it works for you? Ah, the ClearAll[f] cleared the TagSet definition (doh!). The better it is! This should make it really minimally invasive.
                    – Henrik Schumacher
                    Nov 4 at 16:48




                    Oh, it works for you? Ah, the ClearAll[f] cleared the TagSet definition (doh!). The better it is! This should make it really minimally invasive.
                    – Henrik Schumacher
                    Nov 4 at 16:48












                    I've added an alternative solution to my answer that was heavily inspired by yours, I hope that's ok (otherwise, feel free to move that part to your answer)
                    – Lukas Lang
                    Nov 4 at 17:17




                    I've added an alternative solution to my answer that was heavily inspired by yours, I hope that's ok (otherwise, feel free to move that part to your answer)
                    – Lukas Lang
                    Nov 4 at 17:17












                    With some variations of this, it is not actually necessary to add the old option to the Options list of the symbol. That means that (other than the renaming), the modifications necessary for backwards compatibility can be kept isolated, and easily removed at some later time. The compromise is that usage with SetOptions is not backwards compatible. But this is rare.
                    – Szabolcs
                    Nov 5 at 9:16




                    With some variations of this, it is not actually necessary to add the old option to the Options list of the symbol. That means that (other than the renaming), the modifications necessary for backwards compatibility can be kept isolated, and easily removed at some later time. The compromise is that usage with SetOptions is not backwards compatible. But this is rare.
                    – Szabolcs
                    Nov 5 at 9:16












                    Hm. I added the old option to the options list because Mathematic would complain about "oldopt" not being a valid option for f...
                    – Henrik Schumacher
                    Nov 5 at 9:19




                    Hm. I added the old option to the options list because Mathematic would complain about "oldopt" not being a valid option for f...
                    – Henrik Schumacher
                    Nov 5 at 9:19


















                     

                    draft saved


                    draft discarded



















































                     


                    draft saved


                    draft discarded














                    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




















































































                    這個網誌中的熱門文章

                    Tangent Lines Diagram Along Smooth Curve

                    Yusuf al-Mu'taman ibn Hud

                    Zucchini