How to make DownValues not reorder function definitions
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]}
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.