From 809409c19758f971fe4f55aeb13150c4d48802d9 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Wed, 25 Apr 2007 20:42:59 +0000 Subject: [PATCH] Add PEP 3141 (named after 3.141), ABCs for numbers, by Jeffrey Jasskin. Not to endorse this (yet) but so we have a record in svn. --- pep-0000.txt | 2 + pep-3141.txt | 481 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 483 insertions(+) create mode 100644 pep-3141.txt diff --git a/pep-0000.txt b/pep-0000.txt index 30213729c..0ecdb129b 100644 --- a/pep-0000.txt +++ b/pep-0000.txt @@ -117,6 +117,7 @@ Index by Category S 3117 Postfix Type Declarations Brandl S 3118 Revising the buffer protocol Oliphant, Banks S 3119 Introducing Abstract Base Classes GvR, Talin + S 3141 A Type Hierarchy for Numbers Yasskin Finished PEPs (done, implemented in Subversion) @@ -473,6 +474,7 @@ Numerical Index S 3117 Postfix Type Declarations Brandl S 3118 Revising the buffer protocol Oliphant, Banks S 3119 Introducing Abstract Base Classes GvR, Talin + S 3141 A Type Hierarchy for Numbers Yasskin Key diff --git a/pep-3141.txt b/pep-3141.txt new file mode 100644 index 000000000..73a9f1447 --- /dev/null +++ b/pep-3141.txt @@ -0,0 +1,481 @@ +PEP: 3141 +Title: A Type Hierarchy for Numbers (and other algebraic entities) +Version: $Revision$ +Last-Modified: $Date$ +Author: Jeffrey Yasskin +Status: Draft +Type: Standards Track +Content-Type: text/x-rst +Created: 23-Apr-2007 +Post-History: Not yet posted + + +Abstract +======== + +This proposal defines a hierarchy of Abstract Base Classes (ABCs) +[#pep3119] to represent numbers and other algebraic entities similar +to numbers. It proposes: + +* A hierarchy of algebraic concepts, including monoids, groups, rings, + and fields with successively more operators and constraints on their + operators. This will be added as a new library module named + "algebra". + +* A hierarchy of specifically numeric types, which can be converted to + and from the native Python types. This will be added as a new + library module named "numbers". + +Rationale +========= + +Functions that take numbers as arguments should be able to determine +the properties of those numbers, and if and when overloading based on +types is added to the language, should be overloadable based on the +types of the arguments. This PEP defines some abstract base classes +that are useful in numerical calculations. A function can check that +variable is an instance of one of these classes and then rely on the +properties specified for them. Of course, the language cannot check +these properties, so where I say something is "guaranteed", I really +just mean that it's one of those properties a user should be able to +rely on. + +This PEP tries to find a balance between providing fine-grained +distinctions and specifying types that few people will ever use. + +Specification +============= + +Although this PEP uses terminology from PEP 3119, the hierarchy is +meaningful for any systematic method of defining sets of +classes. **Todo:** link to the Interfaces PEP when it's ready. I'm +also using the extra notation from [#pep3107] (annotations) to specify +some types. + +Object oriented systems have a general problem in constraining +functions that take two arguments. To take addition as an example, +``int(3) + int(4)`` is defined, and ``vector(1,2,3) + vector(3,4,5)`` +is defined, but ``int(3) + vector(3,4,5)`` doesn't make much sense. So +``a + b`` is not guaranteed to be defined for any two instances of +``AdditiveGroup``, but it is guaranteed to be defined when ``type(a) +== type(b)``. On the other hand, ``+`` does make sense for any sorts +of numbers, so the ``Complex`` ABC refines the properties for plus so +that ``a + b`` is defined whenever ``isinstance(a,Complex) and +isinstance(b,Complex)``, even if ``type(a) != type(b)``. + + +Monoids (http://en.wikipedia.org/wiki/Monoid) consist of a set with an +associative operation, and an identity element under that +operation. **Open issue**: Is a @classmethod the best way to define +constants that depend only on the type?:: + + class MonoidUnderPlus(Abstract): + """+ is associative but not necessarily commutative and has an + identity given by plus_identity(). + + Subclasses follow the laws: + + a + (b + c) === (a + b) + c + a.plus_identity() + a === a === a + a.plus_identity() + + Sequences are monoids under plus (in Python) but are not + AdditiveGroups. + """ + @abstractmethod + def __add__(self, other): + raise NotImplementedError + + @classmethod + @abstractmethod + def plus_identity(cls): + raise NotImplementedError + +I skip ordinary non-commutative groups here because I don't have any +common examples of groups that use ``+`` as their operator but aren't +commutative. If we find some, the class can be added later.:: + + class AdditiveGroup(MonoidUnderPlus): + """Defines a commutative group whose operator is +, and whose inverses + are produced by -x. + See http://en.wikipedia.org/wiki/Abelian_group. + + Where a, b, and c are instances of the same subclass of + AdditiveGroup, the operations should follow these laws, where + 'zero' is a.__class__.zero(). + + a + b === b + a + (a + b) + c === a + (b + c) + zero + a === a + a + (-a) === zero + a - b === a + -b + + Some abstract subclasses, such as Complex, may extend the + definition of + to heterogenous subclasses, but AdditiveGroup only + guarantees it's defined on arguments of exactly the same types. + + Vectors are AdditiveGroups but are not Rings. + """ + @abstractmethod + def __add__(self, other): + """Associative commutative operation, whose inverse is negation.""" + raise NotImplementedError + + # **Open issue:** Do we want to give people a choice of which of the + # following to define, or should we pick one arbitrarily?:: + + def __neg__(self): + """Must define this or __sub__().""" + return self.zero() - self + + def __sub__(self, other): + """Must define this or __neg__().""" + return self + -other + + @classmethod + @abstractmethod + def zero(cls): + """A better name for +'s identity as we move into more mathematical + domains.""" + raise NotImplementedError + + @classmethod + def plus_identity(cls): + return cls.zero() + +Including Semiring (http://en.wikipedia.org/wiki/Semiring) would help +a little with defining a type for the natural numbers. That can be +split out once someone needs it (see ``IntegralDomain`` for how).:: + + class Ring(AdditiveGroup): + """A mathematical ring over the operations + and *. + See http://en.wikipedia.org/wiki/Ring_%28mathematics%29. + + In addition to the requirements of the AdditiveGroup superclass, a + Ring has an associative but not necessarily commutative + multiplication operation with identity (one) that distributes over + addition. A Ring can be constructed from any integer 'i' by adding + 'one' to itself 'i' times. When R is a subclass of Ring, the + additive identity is R(0), and the multiplicative identity is + R(1). + + Matrices are Rings but not Commutative Rings or Division + Rings. The quaternions are a Division Ring but not a + Field. The integers are a Commutative Ring but not a Field. + """ + @abstractmethod + def __init__(self, i:int): + """An instance of a Ring may be constructed from an integer. + + This may be a lossy conversion, as in the case of the integers + modulo N.""" + pass + + @abstractmethod + def __mul__(self, other): + """Satisfies: + a * (b * c) === (a * b) * c + one * a === a + a * one === a + a * (b + c) === a * b + a * c + + where one == a.__class__(1) + """ + raise NotImplementedError + + @classmethod + def zero(cls): + return cls(0) + + @classmethod + def one(cls): + return cls(1) + +I'm skipping both CommutativeRing and DivisionRing here.:: + + class Field(Ring): + """The class Field adds to Ring the requirement that * be a + commutative group operation except that zero does not have an + inverse. + See http://en.wikipedia.org/wiki/Field_%28mathematics%29. + + Practically, that means we can define division on a Field. The + additional laws are: + + a * b === b * a + a / a === a.__class_(1) # when a != a.__class__(0) + + Division lets us construct a Field from any Python float, + although the conversion is likely to be lossy. Some Fields + include the real numbers, rationals, and integers mod a + prime. Python's ``float`` resembles a Field closely. + """ + def __init__(self, f:float): + """A Field should be constructible from any rational number, which + includes Python floats.""" + pass + + @abstractmethod + def __div__(self, divisor): + raise NotImplementedError + +Division is somewhat complicated in Python. You have both __floordiv__ +and __div__, and ints produce floats when they're divided. For the +purposes of this hierarchy, ``__floordiv__(a, b)`` is defined by +``floor(__div__(a, b))``, and, since int is not a subclass of Field, +it's allowed to do whatever it wants with __div__. + +There are four more reasonable classes that I'm skipping here in the +interest of keeping the initial library simple. They are: + +``Algebraic`` + Rational powers of its elements are defined (and maybe a few other + operations) + (http://en.wikipedia.org/wiki/Algebraic_number). Complex numbers + are the most well-known algebraic set. Real numbers are _not_ + algebraic, but Python does define these operations on floats, + which makes defining this class somewhat difficult. + +``Trancendental`` + The elementary functions + (http://en.wikipedia.org/wiki/Elementary_function) are + defined. These are basically arbitrary powers, trig functions, and + logs, the contents of ``cmath``. + +The following two classes can be reasonably combined with ``Integral`` +for now. + +``IntegralDomain`` + Defines __divmod__. + (http://darcs.haskell.org/numericprelude/docs/html/Algebra-IntegralDomain.html#t%3AC) + +``PrincipalIdealDomain`` + Defines gcd and lcm. + (http://darcs.haskell.org/numericprelude/docs/html/Algebra-PrincipalIdealDomain.html#t%3AC) + +If someone needs to split them later, they can use code like:: + import numbers + class IntegralDomain(Ring): ... + numbers.Integral.__bases__ = (IntegralDomain,) + numbers.Integral.__bases__ + + +Finally, we get to numbers. This is where we switch from the "algebra" +module to the "numbers" module.:: + + class Complex(Ring, Hashable): + """The ``Complex`` ABC indicates that the value lies somewhere + on the complex plane, not that it in fact has a complex + component: ``int`` is a subclass of ``Complex``. Because these + actually represent complex numbers, they can be converted to + the ``complex`` type. + + ``Complex`` finally gets around to requiring its subtypes to + be immutable so they can be hashed in a standard way. + + ``Complex`` also requires its operations to accept + heterogenous arguments. Subclasses should override the + operators to be more accurate when they can, but should fall + back on the default definitions to handle arguments of + different (Complex) types. + + **Open issue:** __abs__ doesn't fit here because it doesn't + exist for the Gaussian integers + (http://en.wikipedia.org/wiki/Gaussian_integer). In fact, it + only exists for algebraic complex numbers and real numbers. We + could define it in both places, or leave it out of the + ``Complex`` classes entirely and let it be a custom extention + of the ``complex`` type. + + The Gaussian integers are ``Complex`` but not a ``Field``. + """ + @abstractmethod + def __complex__(self): + """Any Complex can be converted to a native complex object.""" + raise NotImplementedError + + def __hash__(self): + return hash(complex(self)) + + @abstractmethod + def real(self) => Real: + raise NotImplementedError + + @abstractmethod + def imag(self) => Real: + raise NotImplementedError + + @abstractmethod + def __add__(self, other): + """The other Ring operations should be implemented similarly.""" + if isinstance(other, Complex): + return complex(self) + complex(other) + else: + return NotImplemented + +``FractionalComplex(Complex, Field)`` might fit here, except that it +wouldn't give us any new operations.:: + + class Real(Complex, TotallyOrdered): + """Numbers along the real line. Some subclasses of this class + may contain NaNs that are not ordered with the rest of the + instances of that type. Oh well. **Open issue:** what problems + will that cause? Is it worth it in order to get a + straightforward type hierarchy? + """ + @abstractmethod + def __float__(self): + raise NotImplementedError + def __complex__(self): + return complex(float(self)) + def real(self) => self.__class__: + return self + def imag(self) => self.__class__: + return self.__class__(0) + def __abs__(self) => self.__class__: + if self < 0: return -self + else: return self + + + class FractionalReal(Real, Field): + """Rationals and floats. This class provides concrete + definitions of the other four methods from properfraction and + allows you to convert fractional reals to integers in a + disciplined way. + """ + @abstractmethod + def properfraction(self) => (int, self.__class__): + """Returns a pair (n,f) such that self == n+f, and: + * n is an integral number with the same sign as self; and + * f is a fraction with the same type and sign as self, and with + absolute value less than 1. + """ + raise NotImplementedError + def floor(self) => int: + n, r = self.properfraction() + if r < 0 then n - 1 else n + def ceiling(self) => int: ... + def __trunc__(self) => int: ... + def round(self) => int: ... + + +**Open issue:** What's the best name for this class? RealIntegral? Integer?:: + + class Integral(Real): + """Integers!""" + @abstractmethod + def __int__(self): + raise NotImplementedError + def __float__(self): + return float(int(self)) + + @abstractmethod + def __or__(self, other): + raise NotImplementedError + @abstractmethod + def __xor__(self, other): + raise NotImplementedError + @abstractmethod + def __and__(self, other): + raise NotImplementedError + @abstractmethod + def __lshift__(self, other): + raise NotImplementedError + @abstractmethod + def __rshift__(self, other): + raise NotImplementedError + @abstractmethod + def __invert__(self): + raise NotImplementedError + + +Floating point values may not exactly obey several of the properties +you would expect from their superclasses. For example, it is possible +for ``(large_val + -large_val) + 3 == 3``, but ``large_val + +(-large_val + 3) == 0``. On the values most functions deal with this +isn't a problem, but it is something to be aware of. Types like this +inherit from ``FloatingReal`` so that functions that care can know to +use a numerically stable algorithm on them. **Open issue:** Is this +the proper way to handle floating types?:: + + class FloatingReal: + """A "floating" number is one that is represented as + ``mantissa * radix**exponent`` where mantissa, radix, and + exponent are all integers. Subclasses of FloatingReal don't + follow all the rules you'd expect numbers to follow. If you + really care about the answer, you have to use numerically + stable algorithms, whatever those are. + + **Open issue:** What other operations would be useful here? + + These include floats and Decimals. + """ + @classmethod + @abstractmethod + def radix(cls) => int: + raise NotImplementedError + + @classmethod + @abstractmethod + def digits(cls) => int: + """The number of significant digits of base cls.radix().""" + raise NotImplementedError + + @classmethod + @abstractmethod + def exponentRange(cls) => (int, int): + """A pair of the (lowest,highest) values possible in the exponent.""" + raise NotImplementedError + + @abstractmethod + def decode(self) => (int, int): + """Returns a pair (mantissa, exponent) such that + mantissa*self.radix()**exponent == self.""" + raise NotImplementedError + + + +Inspiration +=========== +http://hackage.haskell.org/trac/haskell-prime/wiki/StandardClasses +http://repetae.net/john/recent/out/classalias.html + +References +========== + +.. [#pep3119] Introducing Abstract Base Classes + (http://www.python.org/dev/peps/pep-3119/) + +.. [#pep3107] Function Annotations + (http://www.python.org/dev/peps/pep-3107/) + +.. [3] Possible Python 3K Class Tree?, wiki page created by Bill Janssen + (http://wiki.python.org/moin/AbstractBaseClasses) + +.. [#numericprelude] NumericPrelude: An experimental alternative + hierarchy of numeric type classes + (http://darcs.haskell.org/numericprelude/docs/html/index.html) + + +Acknowledgements +---------------- + +Thanks to Neil Norwitz for helping me through the PEP process. + +The Haskell Numeric Prelude [#numericprelude] nicely condensed a lot +of experience with the Haskell numeric hierarchy into a form that was +relatively easily adaptable to Python. + +Copyright +========= + +This document has been placed in the public domain. + + + +.. + Local Variables: + mode: indented-text + indent-tabs-mode: nil + sentence-end-double-space: t + fill-column: 70 + coding: utf-8 + End: