How can one manually change the rule ordering
General
The definitions get reordered at definition-time by a part of the pattern matcher, that takes care of automatic rule reordering. It does so, based on relative generality of rules, as far as it is able to determine that. This is not always possible, so when it can't determine which of the two rules is more general, it appends the rules to DownValues
(or SubValues
, UpValues
, etc.) in the order the definitions are given. This is described in the documentation. Some past discussions on this site, containing more information about that, can be found here and here.
Manipualations with DownValues
As mentioned in comments and the other answer, one general way to change the order of definitions is to manipulate DownValues
directly, assigning to DownValues[f]
the rules in the order you want. This technique has been described in the documentation, and also extensively in David Wagner's book (which is available for free).
The most general way is indeed
DownValues[f] = {rules}
However, sometimes a more special form of rule rearrangement is handy: if you give a new definition, which you want to be tried first, but which you know for sure to be added last, you can do this:
f[...]:=your-new-definition;
DownValues[f] = RotateRight[DownValues[f]]
In which case, your definitions becomes the first, while all the other definitions maintain the same relative order as before. This trick has been discussed by Wagner in his book. Another example where this trick has been put to use, is here.
Using symbolic tags to fool the reordering system
This trick I haven't seen used by others, although I am sure I was not the only one to come up with it. Basically, you do something like this:
ClearAll[f, $tag];
f[x_] /; ($tag; True) := Sin[x];
f[x_?EvenQ] := x;
f[x_?OddQ] := x^2;
The pattern-matcher can no longer decide that the first rule is more general than the others, since it can't know what $tag
is, until the code runs. In practice, $tag
should have no value, and serves only to ensure that rules aren't reordered. It is also convenient since, if you no longer need such definition, you can simply do
DownValues[f] = DeleteCases[DownValues[f], def_/;!FreeQ[def, $tag]]
When it breaks
One other subtle point, that tends to be overlooked, is that definitions which don't contain patterns (underscores and other pattern-building blocks), are stored in a separate hash-table internally. In DownValues
list, they always come first - since indeed, they are always more specific than those containing patterns. And no matter how you reorder DownValues
, you can't bring those "down" the definitions list. For example:
ClearAll[ff, $tag];
ff[x_] /; ($tag; True) := Sin[x];
ff[x_?EvenQ] := x;
ff[x_?OddQ] := x^2;
ff[0] = 0;
ff[1] = 10;
Let's check now:
DownValues[ff]
(*
{HoldPattern[ff[0]] :> 0, HoldPattern[ff[1]] :> 10,
HoldPattern[ff[x_] /; ($tag; True)] :> Sin[x],
HoldPattern[ff[x_?EvenQ]] :> x, HoldPattern[ff[x_?OddQ]] :> x^2}
*)
We can attempt to reorder manually:
DownValues[ff] = DownValues[ff][[{3, 4, 5, 1, 2}]]
only to discover that this didn't work:
DownValues[ff]
(*
{HoldPattern[ff[0]] :> 0, HoldPattern[ff[1]] :> 10,
HoldPattern[ff[x_] /; ($tag; True)] :> Sin[x],
HoldPattern[ff[x_?EvenQ]] :> x, HoldPattern[ff[x_?OddQ]] :> x^2}
*)
In some sense, this is good, because e.g. this makes standard memoization idiom f[x_]:=f[x]=...
both possible and stable / robust. But this is something to keep in mind.
You can still make these definitions be the last by using the tag-trick:
ClearAll[ff, $tag, $tag1];
ff[x_] /; ($tag; True) := Sin[x];
ff[x_?EvenQ] := x;
ff[x_?OddQ] := x^2;
ff[0] /; ($tag1; True) = 0;
ff[1] /; ($tag1; True) = 10;
So that
ff[1]
(* Sin[1] *)
But then you considerably slow down the lookup for such definitions, even when they eventually fire.
Actually we have direct control over this via a System Option. Set:
SetSystemOptions["DefinitionsReordering" -> "None"];
Then:
Clear[f];
f[x_] := Sin[x];
f[x_?EvenQ] := x;
f[x_?OddQ] := x^2;
{f[1], f[2], f[3], f[4], f[3/2], f[Newton]}
{Sin[1], Sin[2], Sin[3], Sin[4], Sin[3/2], Sin[Newton]}
Restore the default behavior with:
SetSystemOptions["DefinitionsReordering" -> "Default"];
By the way this reordering takes significant time; by disabling it I was able to make an already efficient solution more than twice as fast for How to select minimal subsets?
Prophylactic
Leonid expressed concern about the generality of the effect of this setting. Here is an attempt to localize its behavior.
SetAttributes[nonOrder, {HoldFirst, SequenceHold}]
nonOrder[{body_}] :=
Internal`WithLocalSettings[
SetSystemOptions["DefinitionsReordering" -> "None"],
body,
SetSystemOptions["DefinitionsReordering" -> "Default"]
]
nonOrder[LHS_ = RHS_] := nonOrder[LHS, RHS]
nonOrder[LHS_, RHS_] := nonOrder @ {LHS = Unevaluated @ RHS}
nonOrder[sd : (LHS_ := RHS_)] := nonOrder @ {sd}
With this internal definitions made during the evaluation of the right hand side in Set
are ordered normally.
ClearAll[f, foo, bar, baz]
foo[] := (bar[x_] := Sin[x]; bar[x_?OddQ] := x^2; baz)
nonOrder[ f[1] = foo[] ];
nonOrder[ f[x_ /; x > 1] := 2 + 2 ]
?f
?bar
Global`f f[1]=baz f[x_/;x>1]:=2+2 Global`bar bar[x_?OddQ]:=x^2 bar[x_]:=Sin[x]
Note that f
is not automatically ordered, as intended, while bar
is ordered, as intended.
This is more of an extended comment in response to
@LLlAMnYP is there a way to do this without knowing the existing definitions in advance? or maybe use Prepend?
You can roll a function like so:
SetAttributes[makeDef, HoldAllComplete]
makeDef[f_Symbol, expr_, n_Integer] :=
Module[{dVal},
Block[{f}, Evaluate[expr]; dVal = First[DownValues[f]]];
DownValues[f] = Insert[DownValues[f], dVal, n];]
Then
f[x_?EvenQ] := x;
f[x_?OddQ] := x^2;
DownValues[f]
{HoldPattern[f[x_?EvenQ]] :> x, HoldPattern[f[x_?OddQ]] :> x^2}
makeDef[f, f[x_] := Sin[x], 1]
DownValues[f]
{HoldPattern[f[x_]] :> Sin[x], HoldPattern[f[x_?EvenQ]] :> x, HoldPattern[f[x_?OddQ]] :> x^2}
makeDef
takes the symbol for which rules are being set as the first argument, the expression creating the rule as the second argument, and the position at which the rule will be inserted as the third argument.