TransWikia.com

How to make DownValues not reorder function definitions

Mathematica Asked on May 11, 2021

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.

2 Answers

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]}

Correct answer by Carl Woll on May 11, 2021

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.

Answered by Leonid Shifrin on May 11, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP