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]}
- 124,525
- 11
- 401
- 574
- 130,679
- 6
- 243
- 355
-
1Nice, 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. – Leonid Shifrin Feb 11 '21 at 16:48
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.
- 114,335
- 15
- 329
- 420
DownValues[mySymbol] = <explicit list of definitions>or have just oneDownValuelikemySymbol[args__] := Switch[{args}, <handle all cases here>], otherwise the reordering is out of your hands. – Jason B. Feb 11 '21 at 15:56