I learned about this approach from using Magma:
%magma R<x> := PolynomialRing(Integers()); print Parent(x); /// Univariate Polynomial Ring in x over Integer Ring
(In this blog post I'll put %magma above code that gets input to Magma; all other code gets input to Sage. The input and output is separated by ///.)
R.<x> = ZZ parent(x) /// Univariate Polynomial Ring in x over Integer Ring
x.parent() /// Univariate Polynomial Ring in x over Integer Ring
isinstance(ZZ, Parent) /// True
isinstance(2, Parent) /// False
"The primary goal of coercion is to be able to transparently do arithmetic, comparisons, etc. between elements of distinct parents."
When I used to try to get people to use Magma, perhaps the number one complaint I heard about Magma was that doing arithmetic with objects having distinct parents was difficult and frustrating.
For the first year, in Sage, there was a very simple coercion system:
- If you try to compute a + b or a * b, first somehow put b into the parent of a, then do the arithmetic.
That seriously sucked. E.g.,
Mod(2,7) + 6
was completely different than
6 + Mod(2,7)!
The first was Mod(1,7), and the second was the integer 8. This makes understanding code difficult and unpredictable.
So I rewrote coercion to be a bit better (this was a really painful rewrite that I mostly did myself over several hard months of work):
- If you try to compute a + b (or a*b), check for a "canonical coercions" from the parent of a into b, or failing that, from the parent of b into a. If there aren't any raise an error. If there is one, use it. There won't be both unless there is some canonical isomorphism.
- There are some axioms about what a canonical coercion is. At least it is homomorphism.
Then we decided that there is a canonical homomorphism Z --> Z/7Z, but there is not one Z/7Z --> Z since there is no ring homomorphism in this direction, hence the above makes sense in either order.
One implication of this new model was that parent objects have to be immutable, i.e., you can't fundamentally change them after you make them. This is why in Sage you must specify the name of the generator of a polynomial ring at creation time, and can't change it. In Magma, it is typical to specify the name only later if you want.
Objects must be immutable because the canonical maps between them depend on the objects themselves, and we don't want them to just change left and right at runtime.
%magma R := PolynomialRing(RationalField(), 2); f := R.1^3 + 3*R.2^3 - 4/5; print f; /// $.1^3 + 3*$.2^3 - 4/5 [ $.1, $.2 ]
%magma AssignNames(~R, ["x", "y"]); print f; [R.1, R.2] /// x^3 + 3*y^3 - 4/5 [x, y]
%magma AssignNames(~R, ["z", "w"]); print f; /// z^3 + 3*w^3 - 4/5
Now in Sage:
R = PolynomialRing(QQ) /// TypeError: You must specify the names of the variables.
R.<x,y> = PolynomialRing(QQ) f = x^3 + 3*y^3 - 4/5; f /// x^3 + 3*y^3 - 4/5
Note: In Sage, you can can use a with block to temporarily change the names if you really need to for some reason. This is allowed since at the end of the with block the names are guaranteed to be changed back.
with localvars(R, ['z','w']): print f print "back?", f /// z^3 + 3*w^3 - 4/5 back? x^3 + 3*y^3 - 4/5
But this new model had a major problem too, e.g., if x in Z[x] then "x + 1/2" would FAILS! This is because 1/2 does not coerce into Z[x] (the parent of x), and x does not coerce into Q (the parent of 1/2).
Maybe the implementors of Magma have the answers? Evidently not.
%magma R<x> := PolynomialRing(Integers()); x + 1/2; /// Runtime error in '+': Bad argument types Argument types given: RngUPolElt[RngInt], FldRatElt
Robert Bradshaw did though, and now it is in Sage:
R.<x> = ZZ x + 1/2 /// x + 1/2
His new design is (for the most part) what Sage actually uses now.
He launched an effort in 2008 (see the Dev Days 1 Wiki) to implement a rewrite of the coercion model to his new design. This ended up swallowing up half the development effort at the workshop, and was a massive amount of work, since every parent structure and element had to have some modifications made to it.
This meant people changing a lot of code all over Sage that they didn't necessarily understand, and crossing their fingers that the doctest test suite would catch their mistakes. This was SCARY. After much work, none of this went into Sage. It was just way too risky. This failure temporarily (!) burned out some developers.
Robert Bradshaw, on the other hand, persisted and came up with a new approach that involved migrating Sage code gradually. I.e., he made it so that the old coercion model was still fully supported simultaneously with the new one, then he migrated a couple of parent structures, and got the code into Sage. I'm sure not everything is migrated, even today. There are two points to what he did:
- He extended the rules so x + 1/2 works, i.e., the result of a+b need not live in the parent of a or the parent of b.
- He made implementing coercion much more top down: simply implement various methods in a class that derives from Parent. This meant that instead of coercion being rules and conventions that people have to understand and implement in their own code all over Sage, they just implement a small amount of code and the rules (and benefits) are all enforced automatically.
The Coercion Model
The coercion model is explained here: http://sagemath.org/doc/reference/coercion.html