5
$\begingroup$

What can I do to prevent DownValues from reordering my function definitions?Is there a simple way to fix it? Any suggestion will be greatly appreciated.

$\endgroup$
6
  • 4
    $\begingroup$ It would be good if you give a specific example of behavior that you want to change, the current behavior and what you would want instead. $\endgroup$ Feb 11, 2021 at 15:16
  • $\begingroup$ Example: mathematica.stackexchange.com/a/127713/4999 $\endgroup$
    – Michael E2
    Feb 11, 2021 at 15:28
  • $\begingroup$ @MichaelE2 The reason I asked is because there may be several strategies, depending on the case and exact setup. $\endgroup$ Feb 11, 2021 at 15:35
  • $\begingroup$ @LeonidShifrin thanks!,i have no some concrete example, what i want is a method can work for every case that what i enter is what i want,the reordering is fordidden. $\endgroup$ Feb 11, 2021 at 15:53
  • 2
    $\begingroup$ @LeonidShifrin Yes, I had upvoted your comment for that reason. I have also found that giving a user an explicit example makes them realize that they need to clarify how their situation is different. $\endgroup$
    – Michael E2
    Feb 11, 2021 at 16:06

2 Answers 2

10
$\begingroup$

You could change a system option to achieve your goal:

SetSystemOptions["DefinitionsReordering"->None];

Clear[f]
f[x__]:=Print[x]
f[x_]:=x;
f[x_?EvenQ]:=x^2;
f[x_List]:=Length[x]

DownValues[f]

{HoldPattern[f[x__]] :> Print[x], HoldPattern[f[x_]] :> x, HoldPattern[f[x_?EvenQ]] :> x^2, HoldPattern[f[x_List]] :> Length[x]}

Of course, it would be better to make a function to temporarily modify this option while defining downvalues, as Leonid does in his code.

SetAttributes[UnorderedDefinition, HoldAll];

UnorderedDefinition[defs_] := With[{old = SystemOptions["DefinitionsReordering"]},
    Internal`WithLocalSettings[
        SetSystemOptions["DefinitionsReordering"->None],
        
        defs,
        
        SetSystemOptions[old]
    ]
]

(replace Internal`WithLocalSettings with WithCleanup if using version 12.2)

Then:

Clear[f]

UnorderedDefinition[
    f[x__]:=Print[x];
    f[x_]:=x;
    f[x_?EvenQ]:=x^2;
    f[x_List]:=Length[x]
]

DownValues[f]

{HoldPattern[f[x__]] :> Print[x], HoldPattern[f[x_]] :> x, HoldPattern[f[x_?EvenQ]] :> x^2, HoldPattern[f[x_List]] :> Length[x]}

$\endgroup$
1
  • 1
    $\begingroup$ Nice, I forgot about this option. OTOH, I would not touch this global system option even temporarily, who knows what other code will be evaluated during the execution of my definitions (e.g. autoloading), and may be affected by this. Such possibility is admittedly rather unlikely, but the bugs coming from such cases, will be nearly impossible to catch. +1 anyway, good to be reminded about this option. $\endgroup$ Feb 11, 2021 at 16:48
7
$\begingroup$

An idea and a simple implementation

Here is one possible way to achieve what you want: define a wrapper which would contain all your definitions, remember their original order, and reorder them after they have been evaluated:

ClearAll[defineOrdered]
SetAttributes[defineOrdered, HoldAll];
defineOrdered[func_Symbol, definitions__SetDelayed] :=
  Module[{defIndex},
    MapIndexed[
      Function[{def, pos},
        With[{index = pos[[1]]},
          Replace[
            Unevaluated[def],
            Verbatim[SetDelayed][lhs_, rhs_] :> SetDelayed[lhs, defIndex[index, rhs]]
          ]
        ],
        HoldAll
      ],
      Unevaluated[definitions]
    ];
    DownValues[func]  = #[[All, 2]] & @ SortBy[First] @ Replace[
      DownValues[func],
      Verbatim[RuleDelayed][lhs_,  defIndex[index_, rhs_]] :> 
          {index, RuleDelayed[lhs, rhs]},
      {1}
    ]
]

You can use it as:

ClearAll[f]
defineOrdered[
  f
  , 
  f[x__] := Print[x],
  f[x_] := x,
  f[x_?EvenQ] := x^2,
  f[x_List] := Length[x]
]

The resulting definitions are exactly in the order they were given. Note that the definitions inside defineOrdered must be comma-separated.

Note however that this can produce nonsensical results, which is illustrated by the above contrived example - where not reordering definitions would render a number of more specific ones completely unreachable, shadowed by more general ones.

Limitations

I have not included other assignment operators (Set, TagSetDelayed, TagSet, etc.), but that can be done straightforwardly if necessary.


Note also that my simplistic code above will break in some more subtle cases, such as e.g. conditional definitions with shared local variables. Consider an example:

ClearAll[g]
defineOrdered[
  g
  , 
  g[x_] := With[{y = x^2}, x /; y > 20],
  g[x_] := 10
]

(* {HoldPattern[g[x_]] :> 10} *)

We see that the second definition has overridden the first one, which in this case should not have happened:

ClearAll[g]
g[x_] := With[{y = x^2}, x /; y > 20]
g[x_] := 10
DownValues[g]

(* 
  {HoldPattern[g[x_]] :> With[{y = x^2}, x /; y > 20], HoldPattern[g[x_]] :> 10}
*)

because the first definition is conditional.

Such cases can also be handled, with a somewhat more complicated definition indexing scheme.


The conclusion here is that the above implementation of defineOrdered is a proof of concept, not a production-level code.

Definitions not containing patterns

The last comment is about definitions not containing patterns. While it probably should not matter in this case, keep in mind that defineOrdered will not cause DownValues to store the original order:

ClearAll[ff]
defineOrdered[
  ff
  ,
  ff[3] := 1,
  ff[2] := 2,
  ff[1] := 3
]; 

DownValues[ff]

(* {HoldPattern[ff[1]] :> 3, HoldPattern[ff[2]] :> 2,HoldPattern[ff[3]] :> 1} *)

which happens because such definitions are stored by DownValues separately, in an internal hash-table, and are reordered automatically, no matter what.

If for some reason you need the original order in such cases, you can use the Sort -> False option setting:

DownValues[ff, Sort -> False]

(* {HoldPattern[ff[3]] :> 1, HoldPattern[ff[2]] :> 2, HoldPattern[ff[1]] :> 3} *)

Although, again, in this case it probably should not normally matter, because such definitions usually do not shadow each other.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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