Overall I agree with the direction that while we should be _inspired by_
algebraic structures, we should not slavishly adhere to them, because
these are not platonic numbers, and they fail to obey the algebraic
axioms in all sorts of ways.
I think we can borrow much hard-won experience from our friends in the
Haskell community. For example, `Num a` defines both (+) and (*), and
suggests the existence of additive and multiplicative identities, but
appeals not to ring-ness but to something more like "be reasonable".
Specifically, it says "looks like a ring, lots of Nums are rings, but
actually requiring it would be too restrictive." And despite the
seeming complexity of the Haskell numeric tower, there is much
(sensible) lumping going on in the base Num class. (All of these
properties seem like candidates for shameless stealing.)
Haskell also relegates the algebraic structure (ring, monoid, etc) to a
different corner of the field (heh), separate from the types describing
numerics, and doesn't attempt to use symbolic operators, instead using
nominal functions like `mzero`.
The point about implementing both `Num` and `Ord` is one of those
"obvious not obvious" statements; given that an ordered ring includes
additional axioms above the union of the ring and ordering axioms, it
would not be reasonable to expect "witnesses Ord" and "witnesses Ring"
to mean "witnesses OrderedRing". Instead, one would need an OrderedRing
type class, which extends Ord and Eq, and adds additional laws.
The Haskell Report defines no laws for |Num
<https://hackage-content.haskell.org/package/base-4.22.0.0/docs/GHC-Num.html#t:Num>|.
However, |(|+
<https://hackage-content.haskell.org/package/base-4.22.0.0/docs/GHC-Num.html#v:-43->|)|
and |(|*
<https://hackage-content.haskell.org/package/base-4.22.0.0/docs/GHC-Num.html#v:-42->|)|
are customarily expected to define a ring and have the following
properties:
*Associativity of |(|+
<https://hackage-content.haskell.org/package/base-4.22.0.0/docs/GHC-Num.html#v:-43->|)|*
|(x + y) + z| = |x + (y + z)|
*Commutativity of |(|+
<https://hackage-content.haskell.org/package/base-4.22.0.0/docs/GHC-Num.html#v:-43->|)|*
|x + y| = |y + x|
*||fromInteger
<https://hackage-content.haskell.org/package/base-4.22.0.0/docs/GHC-Num.html#v:fromInteger>|
0| is the additive identity*
|x + fromInteger 0| = |x|
*|negate
<https://hackage-content.haskell.org/package/base-4.22.0.0/docs/GHC-Num.html#v:negate>|
gives the additive inverse*
|x + negate x| = |fromInteger 0|
*Associativity of |(|*
<https://hackage-content.haskell.org/package/base-4.22.0.0/docs/GHC-Num.html#v:-42->|)|*
|(x * y) * z| = |x * (y * z)|
*||fromInteger
<https://hackage-content.haskell.org/package/base-4.22.0.0/docs/GHC-Num.html#v:fromInteger>|
1| is the multiplicative identity*
|x * fromInteger 1| = |x| and |fromInteger 1 * x| = |x|
*Distributivity of |(|*
<https://hackage-content.haskell.org/package/base-4.22.0.0/docs/GHC-Num.html#v:-42->|)|
with respect to |(|+
<https://hackage-content.haskell.org/package/base-4.22.0.0/docs/GHC-Num.html#v:-43->|)|*
|a * (b + c)| = |(a * b) + (a * c)| and |(b + c) * a| = |(b * a) +
(c * a)|
*Coherence with |toInteger|*
if the type also implements |Integral
<https://hackage-content.haskell.org/package/base-4.22.0.0/docs/GHC-Real.html#v:Integral>|,
then |fromInteger
<https://hackage-content.haskell.org/package/base-4.22.0.0/docs/GHC-Num.html#v:fromInteger>|
is a left inverse for |toInteger
<https://hackage-content.haskell.org/package/ghc-internal-9.1401.0/docs/GHC-Internal-Real.html#v:toInteger>|,
i.e. |fromInteger (toInteger i) == i|
Note that it /isn't/ customarily expected that a type instance of both
|Num
<https://hackage-content.haskell.org/package/base-4.22.0.0/docs/GHC-Num.html#t:Num>|
and |Ord
<https://hackage-content.haskell.org/package/base-4.22.0.0/docs/Data-Ord.html#t:Ord>|
implement an ordered ring. Indeed, in |base| only |Integer
<https://hackage-content.haskell.org/package/base-4.22.0.0/docs/Prelude.html#t:Integer>|
and |Rational
<https://hackage-content.haskell.org/package/base-4.22.0.0/docs/Data-Ratio.html#v:Rational>|
do.
One of the implicit design question here is "are these the interfaces that allow operator
overloading?" or "are these the interfaces that declare various algebraic
properties?"
Since many of the numerical types of possible interest are closer to, say,
floating-point-like types that have few algebraic properties rather than
integer-like types that have more, I don't want to preclude floating-point-like
types from participating in operator overloading because of their lack of
strong algebraic properties. Matrices/vectors would also fall closer to
floating-point-like rather than integer-like and I would not want to preclude
matrices/vectors from benefiting from operators.
I included a slide in my 2025 JVMLS talk on numerics speculating that a future refinement of these kinds of
interfaces could include an idiom to indicate "yes, this type actually obeys the ring axioms" or
"... the field axioms", etc., but that is not included in this early "lumpy" iteration.
I would fully expect some evolution of the set of interfaces, what methods go
where, the set of interfaces, etc. as we can more experience using these trial
types with type classes.
Thanks.
-------------
PR Review
Comment:https://git.openjdk.org/valhalla/pull/1917#discussion_r2700497372