From ae3e4586cdf4b1f10e0f587f12eb390d38344bd1 Mon Sep 17 00:00:00 2001 From: Ryan Hagenson Date: Sun, 5 Jul 2020 17:48:50 -0500 Subject: [PATCH 01/12] Initial expand math draft --- text/0000-expand-math.md | 102 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 text/0000-expand-math.md diff --git a/text/0000-expand-math.md b/text/0000-expand-math.md new file mode 100644 index 00000000..336e6e9a --- /dev/null +++ b/text/0000-expand-math.md @@ -0,0 +1,102 @@ +- Feature Name: Expand `math` +- Start Date: 2020-07-05 +- RFC PR: +- Pony Issue: + +# Summary + +This RFC proposes expanding the standard library's `math` package and the shape it might take to leverage the full extent of Pony. + +# Motivation + +Currently, `math` includes only a `Fibonacci` class. This should be expanded to include math types, constants, and present a structure with further expansion in mind. An expanded `math` library will allow a unified mathematics among Pony developers. + +# Detailed design + +The primary goals of this initial expansion are: 1) (re)structure the library into distinct sub-packages and 2) provide common `math` data types. + +Subpackaging and explicit name spacing is intended to divide concerns rather than have one large `math` library. + +## Structure + +I propose a structure of distinct subpackages including the following: + ++ `math/big`: Arbitrary precision numbers ++ `math/series`: Mathematical series ++ `math/constant`: Mathematical constants ++ `math/rational`: `Rational` data type and related functions ++ `math/complex`: `Complex` data type and related functions ++ `math/(x,exp,etc)`: experimental additions, utilities, and effective "catch-all" for matters that do not neatly fit into other subpackages + +## Common Data Types + +As previewed above in [Structure](#structure), expanding the `math` package should include implementations of common mathematics data types. Below are some implementation proposals for those data types. + +### `math/big` + +Should include `BigInt` and `BigFloat`. + +### `math/series` + +`math/series` should include a `Series` interface which is a superclass of `Iterator`. The purpose of creating a new abstract data type is to generalize functions over mathematical series which do not make sense over iterators -- such as whether a `Series` is diverging or converging, a property that is all but meaningless for an `Iterator`. + +Potential series include `Fibonacci` (already exists), `Pascal` (nCk), `Triangular` ({n+1}C{2}), `Square` (n^2), `Pentagonal` ({2n * (2n - 1)} / 2), etc. + +### `math/constant` + +Initial values to include are those with underlying LLVM representations from the [number namespace](https://llvm.org/doxygen/namespacellvm_1_1numbers.html). + +Once these values exist in `math/constant`, they should be removed from where they are now, which is on [`Float`](https://github.com/ponylang/ponyc/blob/master/packages/builtin/float.pony). + +I foresee this as a primitive `Constant` with methods for each value (e.g., `Constant.pi[A: Float](): A`). + +### `math/rational` + +`math/rational` should decide backing precision on `create[A: Real]` and return precision on `apply][B: Number]` -- `let x = Rational.create[U64](where numerator=2)` gives a type which is represented by `U64`/`U64` and starts with a value of `2`/`1` which can then be returned via `x.apply[F32](): F32 => F32(2) / F32(1)`. `Rational` should be parameterized only on `Real` types and track sign via an embedded field. + +Changing the underlying precision "in-place" is done via a `prec[C: Real]()` method which creates a new instance of the value with a different precision. + +(I see this package as subsuming `Decimal` and `Fractional` types, as was previously discussed in Zulip.) + +### `math/complex` + +`Complex` should follow similar to `math/rational` in that it is parameterized on `Real` types which are used to represent both the real and imaginary part of the number -- `let x = Complex[U128](7, 2)` is `7 + 2i`. + +Changing the underlying precision "in-place" is done via a `prec[C: Real]()` method which creates a new instance of the value with a different precision. + +### `math/(x,exp,etc)` + +The name is subject to change and I want comments on what such a "catch all" package should be named to clearly denote it is for matters which do not neatly fit elsewhere in my proposed subpackages. + +The explicit intention of this subpackage is to gather useful matters that do not fit into another more dedicated packages. Examples of matters that would be included here are trigonometry and linear algebra functions before corresponding `math/trig` and `math/la` packaged are made. + +# How We Teach This + +I am not familiar with existing materials on Pony numeric types before their introduction in the Tutorial. Given this RFC concerns expanding Pony math types to those commonly seen elsewhere I am unsure any special teaching materials are needed on the matter. Ample documentation and usage examples within the expanded `math` library should be sufficient. + +A new chapter could be added as Tutorial > Packages > Math which walks through the layout and standard usage of Pony `math` in the context of Pony. + +# How We Test This + +I recommend use of `ponycheck` to test all reversible operations pairs (`x+y-y == x`, `x*y/y == x`, etc), precision persistence (`Rational[U8](where numerator=x, denominator=y) * y == x`), and overflow/underflow protection (`Rational[U8](255, 1) + 1 => error`). + +Testing `math` should not affect any other parts of Pony and as such standard CI should suffice. + +# Drawbacks + +The major drawback is additional maintenance cost as well as immediate and continued disagreement around implementation and feature details. + +# Alternatives + +Prior discussion on the expansion of `math` occurred on Zulip [contribute to Pony > math lib](https://ponylang.zulipchat.com/#narrow/stream/192795-contribute-to.20Pony/topic/math.20lib). + +The amount of subpackages is a lot and could be reduced down to one single `math` package if we so choose as none of the proposed additions clash at this time. + +# Unresolved questions + ++ How expansive should the `math` library (whether that is one package or multiple subpackages) become? ++ Does `Rational` mean `Decimal` and/or `Fractional` types are not needed? + +--- + +My personal opinion is that the standard `math` library, in any form, should only include elements of no opinion. That is to say, a `Rational` is numerator over denominator and any implementation reduces down to that form, meanwhile a `Graph` type could be backed by: adjacency list, adjacency matrix, or incidence matrix and given there are multiple options is better left out of the standard library in favor of community implementations. From bd6e0a45e95459039dade186c00a1203f67e3268 Mon Sep 17 00:00:00 2001 From: Ryan Hagenson Date: Mon, 6 Jul 2020 18:15:53 -0500 Subject: [PATCH 02/12] Clarifications --- text/0000-expand-math.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/text/0000-expand-math.md b/text/0000-expand-math.md index 336e6e9a..ef016b8a 100644 --- a/text/0000-expand-math.md +++ b/text/0000-expand-math.md @@ -40,13 +40,13 @@ Should include `BigInt` and `BigFloat`. `math/series` should include a `Series` interface which is a superclass of `Iterator`. The purpose of creating a new abstract data type is to generalize functions over mathematical series which do not make sense over iterators -- such as whether a `Series` is diverging or converging, a property that is all but meaningless for an `Iterator`. -Potential series include `Fibonacci` (already exists), `Pascal` (nCk), `Triangular` ({n+1}C{2}), `Square` (n^2), `Pentagonal` ({2n * (2n - 1)} / 2), etc. +Example series include `Fibonacci` (already exists), `Pascal` (nCk), `Triangular` ({n+1}C{2}), `Square` (n^2), `Pentagonal` ({2n * (2n - 1)} / 2), etc. ### `math/constant` Initial values to include are those with underlying LLVM representations from the [number namespace](https://llvm.org/doxygen/namespacellvm_1_1numbers.html). -Once these values exist in `math/constant`, they should be removed from where they are now, which is on [`Float`](https://github.com/ponylang/ponyc/blob/master/packages/builtin/float.pony). +Once these values exist in `math/constant`, they should be removed from where they are now, which is on `F32` and `F64` of [`Float`](https://github.com/ponylang/ponyc/blob/master/packages/builtin/float.pony). I foresee this as a primitive `Constant` with methods for each value (e.g., `Constant.pi[A: Float](): A`). @@ -62,7 +62,7 @@ Changing the underlying precision "in-place" is done via a `prec[C: Real]()` met `Complex` should follow similar to `math/rational` in that it is parameterized on `Real` types which are used to represent both the real and imaginary part of the number -- `let x = Complex[U128](7, 2)` is `7 + 2i`. -Changing the underlying precision "in-place" is done via a `prec[C: Real]()` method which creates a new instance of the value with a different precision. +Changing the underlying precision is done via a `prec[C: Real]()` method which creates a new instance of the value with a different precision. ### `math/(x,exp,etc)` @@ -94,9 +94,9 @@ The amount of subpackages is a lot and could be reduced down to one single `math # Unresolved questions -+ How expansive should the `math` library (whether that is one package or multiple subpackages) become? -+ Does `Rational` mean `Decimal` and/or `Fractional` types are not needed? ++ How expansive should the `math` library become (whether that is one package or multiple subpackages)? ++ Does having `Rational` mean `Decimal` and/or `Fractional` types are not needed at the moment? --- -My personal opinion is that the standard `math` library, in any form, should only include elements of no opinion. That is to say, a `Rational` is numerator over denominator and any implementation reduces down to that form, meanwhile a `Graph` type could be backed by: adjacency list, adjacency matrix, or incidence matrix and given there are multiple options is better left out of the standard library in favor of community implementations. +My personal opinion is that the standard `math` library, in any form, should only include elements of no opinion. That is to say, a `Rational` is numerator over denominator and any implementation reduces down to that form, meanwhile a `Graph` type could be backed by: adjacency list, adjacency matrix, or incidence matrix, and given there are multiple options is better left out of the standard library in favor of community implementations. From fabfb8402a12dae57d9e2225ff6586645f83abac Mon Sep 17 00:00:00 2001 From: "Ryan A. Hagenson" Date: Mon, 28 Feb 2022 11:55:49 -0600 Subject: [PATCH 03/12] 2022 updates --- text/0000-expand-math.md | 47 ++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/text/0000-expand-math.md b/text/0000-expand-math.md index ef016b8a..7f7fd8f1 100644 --- a/text/0000-expand-math.md +++ b/text/0000-expand-math.md @@ -1,21 +1,22 @@ - Feature Name: Expand `math` -- Start Date: 2020-07-05 +- Start Date: 2022-02-28 - RFC PR: - Pony Issue: # Summary -This RFC proposes expanding the standard library's `math` package and the shape it might take to leverage the full extent of Pony. +This RFC proposes expanding the standard library's `math` package and the shape it might take to leverage the full extent of Pony. There was prior discussion on [Zulip](https://ponylang.zulipchat.com/#narrow/stream/192795-contribute-to.20Pony/topic/math.20lib) years ago, but I expect some opinions to have changed. # Motivation -Currently, `math` includes only a `Fibonacci` class. This should be expanded to include math types, constants, and present a structure with further expansion in mind. An expanded `math` library will allow a unified mathematics among Pony developers. +Currently, `math` includes limited functionality. This should be expanded to include math types, constants, and present a structure with further expansion in mind. An expanded `math` library will allow a unified mathematics among Pony developers. # Detailed design -The primary goals of this initial expansion are: 1) (re)structure the library into distinct sub-packages and 2) provide common `math` data types. +The primary goals of this initial expansion are: -Subpackaging and explicit name spacing is intended to divide concerns rather than have one large `math` library. +1. restructure the `math` library into distinct sub-packages +2. provide common `math` data types ## Structure @@ -34,17 +35,19 @@ As previewed above in [Structure](#structure), expanding the `math` package shou ### `math/big` -Should include `BigInt` and `BigFloat`. +Should include `BigInt`, `BigFloat`, and `BigDecimal` -- see [168](https://github.com/ponylang/rfcs/issues/168) ### `math/series` -`math/series` should include a `Series` interface which is a superclass of `Iterator`. The purpose of creating a new abstract data type is to generalize functions over mathematical series which do not make sense over iterators -- such as whether a `Series` is diverging or converging, a property that is all but meaningless for an `Iterator`. +`math/series` should include a `Series` trait which is a subclass of `Iterator`. The purpose of creating a new abstract data type is to generalize functions over mathematical series which do not make sense over iterators -- such as whether a `Series` is diverging or converging, a property that is all but meaningless for an `Iterator`. Example series include `Fibonacci` (already exists), `Pascal` (nCk), `Triangular` ({n+1}C{2}), `Square` (n^2), `Pentagonal` ({2n * (2n - 1)} / 2), etc. +Current `math/Fibonacci` would go into this series package. + ### `math/constant` -Initial values to include are those with underlying LLVM representations from the [number namespace](https://llvm.org/doxygen/namespacellvm_1_1numbers.html). +Initial values to include are those with underlying LLVM representations from the [numbers namespace](https://llvm.org/doxygen/namespacellvm_1_1numbers.html). Once these values exist in `math/constant`, they should be removed from where they are now, which is on `F32` and `F64` of [`Float`](https://github.com/ponylang/ponyc/blob/master/packages/builtin/float.pony). @@ -52,33 +55,37 @@ I foresee this as a primitive `Constant` with methods for each value (e.g., `Con ### `math/rational` -`math/rational` should decide backing precision on `create[A: Real]` and return precision on `apply][B: Number]` -- `let x = Rational.create[U64](where numerator=2)` gives a type which is represented by `U64`/`U64` and starts with a value of `2`/`1` which can then be returned via `x.apply[F32](): F32 => F32(2) / F32(1)`. `Rational` should be parameterized only on `Real` types and track sign via an embedded field. +`math/rational` should decide backing precision on `create[A: Real]` and return precision on `apply[B: Number]` -- `let x = Rational.create[U64](where numerator = 2)` gives a type which is represented by `U64`/`U64` and starts with a value of `2`/`1` which can then be returned via `x.apply[F32](): F32 => F32(2) / F32(1)`. `Rational` should be parameterized only on `Real` types and track sign via an embedded field. -Changing the underlying precision "in-place" is done via a `prec[C: Real]()` method which creates a new instance of the value with a different precision. +Changing the underlying precision "in-place" is done via a `prec[C: Real]` method which creates a new instance with a different precision. -(I see this package as subsuming `Decimal` and `Fractional` types, as was previously discussed in Zulip.) +(I see this package as subsuming `Decimal` and `Fractional` types, as previously discussed in Zulip.) ### `math/complex` `Complex` should follow similar to `math/rational` in that it is parameterized on `Real` types which are used to represent both the real and imaginary part of the number -- `let x = Complex[U128](7, 2)` is `7 + 2i`. -Changing the underlying precision is done via a `prec[C: Real]()` method which creates a new instance of the value with a different precision. +Changing the underlying precision is done via a `prec[C: Real]` method which creates a new instance of the value with a different precision. ### `math/(x,exp,etc)` -The name is subject to change and I want comments on what such a "catch all" package should be named to clearly denote it is for matters which do not neatly fit elsewhere in my proposed subpackages. +The name is subject to change and I want comments on what such a "catch all" package should be named to clearly denote it is for matters which do not neatly fit elsewhere. The explicit intention of this subpackage is to gather useful matters that do not fit into another more dedicated packages. Examples of matters that would be included here are trigonometry and linear algebra functions before corresponding `math/trig` and `math/la` packaged are made. +Current `math/IsPrime` would go into this catch-all package. + # How We Teach This -I am not familiar with existing materials on Pony numeric types before their introduction in the Tutorial. Given this RFC concerns expanding Pony math types to those commonly seen elsewhere I am unsure any special teaching materials are needed on the matter. Ample documentation and usage examples within the expanded `math` library should be sufficient. +For functionality additions, ample documentation and usage examples within the expanded `math` library should be sufficient. + +I am proposing moving `Fibonacci` and `IsPrime` however nothing about existing functionality should change so teaching users about those updates should follow the pattern of providing example existing code with example modified code showing how to pick up the new locations of existing functionality. -A new chapter could be added as Tutorial > Packages > Math which walks through the layout and standard usage of Pony `math` in the context of Pony. +A new chapter should be added as Tutorial > Packages > Math which walks through the layout and usage of Pony `math`. # How We Test This -I recommend use of `ponycheck` to test all reversible operations pairs (`x+y-y == x`, `x*y/y == x`, etc), precision persistence (`Rational[U8](where numerator=x, denominator=y) * y == x`), and overflow/underflow protection (`Rational[U8](255, 1) + 1 => error`). +I recommend use of `pony_check` to test all reversible operations pairs (`x+y-y == x`, `x*y/y == x`, etc), precision persistence (`Rational[U8](where numerator=x, denominator=y) * y == x`), and overflow/underflow protection (`Rational[U8](255, 1) + 1 => error`). Testing `math` should not affect any other parts of Pony and as such standard CI should suffice. @@ -88,15 +95,9 @@ The major drawback is additional maintenance cost as well as immediate and conti # Alternatives -Prior discussion on the expansion of `math` occurred on Zulip [contribute to Pony > math lib](https://ponylang.zulipchat.com/#narrow/stream/192795-contribute-to.20Pony/topic/math.20lib). - The amount of subpackages is a lot and could be reduced down to one single `math` package if we so choose as none of the proposed additions clash at this time. # Unresolved questions + How expansive should the `math` library become (whether that is one package or multiple subpackages)? -+ Does having `Rational` mean `Decimal` and/or `Fractional` types are not needed at the moment? - ---- - -My personal opinion is that the standard `math` library, in any form, should only include elements of no opinion. That is to say, a `Rational` is numerator over denominator and any implementation reduces down to that form, meanwhile a `Graph` type could be backed by: adjacency list, adjacency matrix, or incidence matrix, and given there are multiple options is better left out of the standard library in favor of community implementations. ++ Do we need all of `Decimal`, `Rational`, and `Fractional` types? I am unaware of their distinction so believe adding a `Rational` (numerator and denominator) type to suffice for current neeeds. From 2add507e810365de53fb865f464dae4b711e609b Mon Sep 17 00:00:00 2001 From: "Ryan A. Hagenson" Date: Mon, 28 Feb 2022 13:43:29 -0600 Subject: [PATCH 04/12] Make constants plural Co-authored-by: Joe Eli McIlvain --- text/0000-expand-math.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-expand-math.md b/text/0000-expand-math.md index 7f7fd8f1..7e150590 100644 --- a/text/0000-expand-math.md +++ b/text/0000-expand-math.md @@ -24,7 +24,7 @@ I propose a structure of distinct subpackages including the following: + `math/big`: Arbitrary precision numbers + `math/series`: Mathematical series -+ `math/constant`: Mathematical constants ++ `math/constants`: Mathematical constants + `math/rational`: `Rational` data type and related functions + `math/complex`: `Complex` data type and related functions + `math/(x,exp,etc)`: experimental additions, utilities, and effective "catch-all" for matters that do not neatly fit into other subpackages From d264e5a5c47aedbcb3c32479efa25dc0a290e551 Mon Sep 17 00:00:00 2001 From: "Ryan A. Hagenson" Date: Tue, 1 Mar 2022 12:45:50 -0600 Subject: [PATCH 05/12] Incorporate initial comments --- text/0000-expand-math.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/text/0000-expand-math.md b/text/0000-expand-math.md index 7e150590..27735e9d 100644 --- a/text/0000-expand-math.md +++ b/text/0000-expand-math.md @@ -15,8 +15,8 @@ Currently, `math` includes limited functionality. This should be expanded to inc The primary goals of this initial expansion are: -1. restructure the `math` library into distinct sub-packages -2. provide common `math` data types +1. restructure the `math` package into distinct subpackages; allowing for separation of concerns over continuing to build a monolithic `math` package +2. provide common `math` data types; for example, `BigInt`, `Rational`, and `Complex` ## Structure @@ -35,6 +35,8 @@ As previewed above in [Structure](#structure), expanding the `math` package shou ### `math/big` +This is a package for arbitary precision numerics. + Should include `BigInt`, `BigFloat`, and `BigDecimal` -- see [168](https://github.com/ponylang/rfcs/issues/168) ### `math/series` @@ -49,13 +51,13 @@ Current `math/Fibonacci` would go into this series package. Initial values to include are those with underlying LLVM representations from the [numbers namespace](https://llvm.org/doxygen/namespacellvm_1_1numbers.html). -Once these values exist in `math/constant`, they should be removed from where they are now, which is on `F32` and `F64` of [`Float`](https://github.com/ponylang/ponyc/blob/master/packages/builtin/float.pony). +Once these values exist in `math/constant`, they could be removed from where they are now, which is on `F32` and `F64` of [`Float`](https://github.com/ponylang/ponyc/blob/master/packages/builtin/float.pony). -I foresee this as a primitive `Constant` with methods for each value (e.g., `Constant.pi[A: Float](): A`). +I foresee this as a primitive `Constant` with methods for each value (e.g., `Constant.pi[A: Float](): A(3.14.159...)`). ### `math/rational` -`math/rational` should decide backing precision on `create[A: Real]` and return precision on `apply[B: Number]` -- `let x = Rational.create[U64](where numerator = 2)` gives a type which is represented by `U64`/`U64` and starts with a value of `2`/`1` which can then be returned via `x.apply[F32](): F32 => F32(2) / F32(1)`. `Rational` should be parameterized only on `Real` types and track sign via an embedded field. +`math/rational` should decide backing precision on `create[A: Real]` and return precision on `apply[B: Number]` -- `let x = Rational.create[U64](where numerator = 2)` gives a type which is represented by embedded `U64`/`U64` and starts with a value of `2`/`1` which can then be returned via `x.apply[F32](): F32 => F32(2) / F32(1)`. `Rational` should be parameterized only on `Real` types and track sign via an embedded field. Changing the underlying precision "in-place" is done via a `prec[C: Real]` method which creates a new instance with a different precision. @@ -63,7 +65,7 @@ Changing the underlying precision "in-place" is done via a `prec[C: Real]` metho ### `math/complex` -`Complex` should follow similar to `math/rational` in that it is parameterized on `Real` types which are used to represent both the real and imaginary part of the number -- `let x = Complex[U128](7, 2)` is `7 + 2i`. +`Complex` should follow similar to `math/rational` in that it is parameterized on `Real` types which are used to represent both the real and imaginary part of the number -- `Complex[U128](7, 2)` is `U128(7) + U128(2)i`. Changing the underlying precision is done via a `prec[C: Real]` method which creates a new instance of the value with a different precision. From ecb460628102e9a8eeb4f2f4e5e768597372c1a2 Mon Sep 17 00:00:00 2001 From: "Ryan A. Hagenson" Date: Tue, 1 Mar 2022 12:54:01 -0600 Subject: [PATCH 06/12] Typo --- text/0000-expand-math.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-expand-math.md b/text/0000-expand-math.md index 27735e9d..0864a521 100644 --- a/text/0000-expand-math.md +++ b/text/0000-expand-math.md @@ -53,7 +53,7 @@ Initial values to include are those with underlying LLVM representations from th Once these values exist in `math/constant`, they could be removed from where they are now, which is on `F32` and `F64` of [`Float`](https://github.com/ponylang/ponyc/blob/master/packages/builtin/float.pony). -I foresee this as a primitive `Constant` with methods for each value (e.g., `Constant.pi[A: Float](): A(3.14.159...)`). +I foresee this as a primitive `Constant` with methods for each value (e.g., `Constant.pi[A: Float](): A(3.14159...)`). ### `math/rational` From 0e08bdfaae172d0d161a397dbc725604c26eeb88 Mon Sep 17 00:00:00 2001 From: Ryan Hagenson Date: Thu, 3 Mar 2022 21:26:10 -0600 Subject: [PATCH 07/12] Draft Rational implementation --- text/0000-expand-math.md | 43 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/text/0000-expand-math.md b/text/0000-expand-math.md index 0864a521..eddf7be5 100644 --- a/text/0000-expand-math.md +++ b/text/0000-expand-math.md @@ -57,7 +57,48 @@ I foresee this as a primitive `Constant` with methods for each value (e.g., `Con ### `math/rational` -`math/rational` should decide backing precision on `create[A: Real]` and return precision on `apply[B: Number]` -- `let x = Rational.create[U64](where numerator = 2)` gives a type which is represented by embedded `U64`/`U64` and starts with a value of `2`/`1` which can then be returned via `x.apply[F32](): F32 => F32(2) / F32(1)`. `Rational` should be parameterized only on `Real` types and track sign via an embedded field. +`math/rational` should decide backing precision on `create[A: UnsignedInteger]` and return precision on `apply[B: Number]` -- `let x = Rational.create[U64](where numerator=2)` gives a type which is represented by `U64(2)`/`U64(2)` which can then be returned as any valid `Number`. `Rational` should be parameterized only on `UnsignedInteger` types and track sign via an internal field. + +```pony +// Parameterized on unsigned integers as negative status is tracked by field +class Rational[A: UnsignedInteger[A] val = USize] + var numerator: A + var denominator: A + var negative: Bool = false + + // Allow for creating Rationals from signed or unsigned integer arguments + new create[B: Integer[B] val = ISize](n: B, d: B = 1)? => + // Error if denominator is zero + if d == B.from[U8](0) then error end + + // Produce unsigned equivalents of arguments + // NOTE: this produces an error of form + // Error: + // main.pony:XX:YY: type argument is outside its constraint + // This is because Integer is not a subtype of UnsignedInteger + let n': A = if n < B.from[U8](0) then A.from[B](-n) else A.from[B](n) end + let n': A = if n < B.from[U8](0) then A.from[B](-n) else A.from[B](n) end + let d': A = if d < B.from[U8](0) then A.from[B](-d) else A.from[B](d) end + + // Find greatest common divisor and reduce fraction + let divisor: A = try GreatestCommonDivisor[A](n', d')? else A.from[U8](1) end + numerator = n' / divisor + denominator = d' / divisor + + // Set negative field if numerator, but not denominator is negative + if (n < B.from[U8](0)) and (B.from[U8](0) < d) then + negative = true + end + + fun string(): String => + // Size of 10 here is a placeholder for determining the size needed + let output = recover String(10) end + if negative then output.append("-") end + output.append(numerator.string()) + output.append(" / ") + output.append(denominator.string()) + output +``` Changing the underlying precision "in-place" is done via a `prec[C: Real]` method which creates a new instance with a different precision. From 5e90235715f4903df67d6c8edbb335fab11d21cb Mon Sep 17 00:00:00 2001 From: Ryan Hagenson Date: Thu, 3 Mar 2022 21:45:53 -0600 Subject: [PATCH 08/12] Add interoperability language to Motivation --- text/0000-expand-math.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/text/0000-expand-math.md b/text/0000-expand-math.md index eddf7be5..aac2d8fb 100644 --- a/text/0000-expand-math.md +++ b/text/0000-expand-math.md @@ -9,11 +9,11 @@ This RFC proposes expanding the standard library's `math` package and the shape # Motivation -Currently, `math` includes limited functionality. This should be expanded to include math types, constants, and present a structure with further expansion in mind. An expanded `math` library will allow a unified mathematics among Pony developers. +Currently, `math` includes limited functionality. This should be expanded to include math types, constants, and present a structure with further expansion in mind. An expanded `math` library will allow a unified mathematics among Pony developers. As was discussed during our [2022-03-01 Sync](https://sync-recordings.ponylang.io/r/2022_03_01.m4a), one core principle for including these types in the stdlib is to have a single canonical implementation of them which allow interoperability of numeric types across the Pony ecosystem. Any numeric types introduced by this RFC **must** existing within the current numerical type hierarchy by being compliant with existing numeric traits. # Detailed design -The primary goals of this initial expansion are: +The primary goals of this initial expansion are: 1. restructure the `math` package into distinct subpackages; allowing for separation of concerns over continuing to build a monolithic `math` package 2. provide common `math` data types; for example, `BigInt`, `Rational`, and `Complex` @@ -143,4 +143,5 @@ The amount of subpackages is a lot and could be reduced down to one single `math # Unresolved questions + How expansive should the `math` library become (whether that is one package or multiple subpackages)? -+ Do we need all of `Decimal`, `Rational`, and `Fractional` types? I am unaware of their distinction so believe adding a `Rational` (numerator and denominator) type to suffice for current neeeds. ++ Do we need all of `Decimal`, `Rational`, and `Fractional` types? I am unaware of their distinction so believe adding a `Rational` (numerator and denominator) type to suffice for current needs. ++ Do we want the types here to be additions to `math` or, since they are really more numeric types, additions alongside other numeric types within `builtin`? From b84afa1fc0bcbbe59a4597e95f079bafdea8d6b3 Mon Sep 17 00:00:00 2001 From: Ryan Hagenson Date: Sun, 6 Mar 2022 11:27:30 -0600 Subject: [PATCH 09/12] Initial mermaid charts --- text/0000-expand-math.md | 93 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 92 insertions(+), 1 deletion(-) diff --git a/text/0000-expand-math.md b/text/0000-expand-math.md index aac2d8fb..bdd83049 100644 --- a/text/0000-expand-math.md +++ b/text/0000-expand-math.md @@ -15,9 +15,100 @@ Currently, `math` includes limited functionality. This should be expanded to inc The primary goals of this initial expansion are: -1. restructure the `math` package into distinct subpackages; allowing for separation of concerns over continuing to build a monolithic `math` package +1. restructure the `math` package into distinct subpackages; allowing for separation of concerns over continuing to build a monolithic `math` package 2. provide common `math` data types; for example, `BigInt`, `Rational`, and `Complex` +## Numeric Hierarchy + +Current the Pony numerics hierarchy is as follows. + +```mermaid +classDiagram +class Any +<> Any +class Real +<> Real +class FloatingPoint +<> FloatingPoint +class Integer +<> Integer +class SignedInteger +<> SignedInteger +class UnsignedInteger +<> UnsignedInteger + +Any <-- Real +Real <-- FloatingPoint +Real <-- Integer +Integer <-- SignedInteger +Integer <-- UnsignedInteger + +FloatingPoint <-- F32 +FloatingPoint <-- F64 + +UnsignedInteger <-- U8 +UnsignedInteger <-- U16 +UnsignedInteger <-- U32 +UnsignedInteger <-- U64 +UnsignedInteger <-- U128 +UnsignedInteger <-- USize +UnsignedInteger <-- ULong + +SignedInteger <-- I8 +SignedInteger <-- I16 +SignedInteger <-- I32 +SignedInteger <-- I64 +SignedInteger <-- I128 +SignedInteger <-- ISize +SignedInteger <-- ILong +``` + +This RFC introduces a few more numeric types: `Rational`, `Complex`, and arbitrary precision `BigInt`. This fit into the hiearchy in the following manner. + +```mermaid +classDiagram +class Any +<> Any +class Real +<> Real +class FloatingPoint +<> FloatingPoint +class Integer +<> Integer +class SignedInteger +<> SignedInteger +class UnsignedInteger +<> UnsignedInteger + +Any <-- Real +Any <-- Complex +Real <-- FloatingPoint +Real <-- Integer +Real <-- Rational +Integer <-- SignedInteger +Integer <-- UnsignedInteger +Integer <-- BigInt + +FloatingPoint <-- F32 +FloatingPoint <-- F64 + +UnsignedInteger <-- U8 +UnsignedInteger <-- U16 +UnsignedInteger <-- U32 +UnsignedInteger <-- U64 +UnsignedInteger <-- U128 +UnsignedInteger <-- USize +UnsignedInteger <-- ULong + +SignedInteger <-- I8 +SignedInteger <-- I16 +SignedInteger <-- I32 +SignedInteger <-- I64 +SignedInteger <-- I128 +SignedInteger <-- ISize +SignedInteger <-- ILong +``` + ## Structure I propose a structure of distinct subpackages including the following: From 103fd97a8827157ef28885f8a8a28ff63696e578 Mon Sep 17 00:00:00 2001 From: Ryan Hagenson Date: Mon, 7 Mar 2022 17:14:36 -0600 Subject: [PATCH 10/12] Add parameterization to new types --- text/0000-expand-math.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/text/0000-expand-math.md b/text/0000-expand-math.md index bdd83049..d89c9210 100644 --- a/text/0000-expand-math.md +++ b/text/0000-expand-math.md @@ -81,16 +81,17 @@ class UnsignedInteger <> UnsignedInteger Any <-- Real -Any <-- Complex +Any <-- Complex["Complex[A: Real]"] Real <-- FloatingPoint Real <-- Integer -Real <-- Rational +Real <-- Rational["Rational[A: Integer]"] Integer <-- SignedInteger Integer <-- UnsignedInteger Integer <-- BigInt FloatingPoint <-- F32 FloatingPoint <-- F64 +FloatingPoint <-- BigFloat UnsignedInteger <-- U8 UnsignedInteger <-- U16 From c8df400143e3b08e27ce25662fcb89a8b649bd10 Mon Sep 17 00:00:00 2001 From: Ryan Hagenson Date: Mon, 7 Mar 2022 17:19:25 -0600 Subject: [PATCH 11/12] Fix parameter syntax --- text/0000-expand-math.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-expand-math.md b/text/0000-expand-math.md index d89c9210..cabf0237 100644 --- a/text/0000-expand-math.md +++ b/text/0000-expand-math.md @@ -81,10 +81,10 @@ class UnsignedInteger <> UnsignedInteger Any <-- Real -Any <-- Complex["Complex[A: Real]"] +Any <-- Complex~Real~ Real <-- FloatingPoint Real <-- Integer -Real <-- Rational["Rational[A: Integer]"] +Real <-- Rational~Integer~ Integer <-- SignedInteger Integer <-- UnsignedInteger Integer <-- BigInt From 3d71de0aa945b00bc7531d3aefab570401f35cfd Mon Sep 17 00:00:00 2001 From: Ryan Hagenson Date: Sun, 13 Mar 2022 18:45:00 -0500 Subject: [PATCH 12/12] Isolate added numerics for dedicated RFC --- text/0000-expand-math.md | 213 +++++++++++++++++++-------------------- 1 file changed, 102 insertions(+), 111 deletions(-) diff --git a/text/0000-expand-math.md b/text/0000-expand-math.md index cabf0237..583bb896 100644 --- a/text/0000-expand-math.md +++ b/text/0000-expand-math.md @@ -1,26 +1,23 @@ -- Feature Name: Expand `math` +- Feature Name: Introduce new numerics -- `Rational`, `BigInt`, `BigFloat`, `Complex` - Start Date: 2022-02-28 - RFC PR: - Pony Issue: # Summary -This RFC proposes expanding the standard library's `math` package and the shape it might take to leverage the full extent of Pony. There was prior discussion on [Zulip](https://ponylang.zulipchat.com/#narrow/stream/192795-contribute-to.20Pony/topic/math.20lib) years ago, but I expect some opinions to have changed. +This RFC proposes the introduction of new numeric types; in particular the addition of a type representing a fractional number (`Rational`), arbitrary precision integer (`BigInt`), arbitrary precision float (`BigFloat`), and complex number (`Complex`). # Motivation -Currently, `math` includes limited functionality. This should be expanded to include math types, constants, and present a structure with further expansion in mind. An expanded `math` library will allow a unified mathematics among Pony developers. As was discussed during our [2022-03-01 Sync](https://sync-recordings.ponylang.io/r/2022_03_01.m4a), one core principle for including these types in the stdlib is to have a single canonical implementation of them which allow interoperability of numeric types across the Pony ecosystem. Any numeric types introduced by this RFC **must** existing within the current numerical type hierarchy by being compliant with existing numeric traits. +The primary motivation for adding these types to the stdlib is to have a single canonical implementation of them which allow interoperability of numeric types across the Pony ecosystem. # Detailed design -The primary goals of this initial expansion are: - -1. restructure the `math` package into distinct subpackages; allowing for separation of concerns over continuing to build a monolithic `math` package -2. provide common `math` data types; for example, `BigInt`, `Rational`, and `Complex` +I propose we add the aforementioned numeric types into `builtin` so they exist alongside the other standard numeric types. These introduced numeric types **must** existing within the current numeric type hierarchy by being compliant with existing numeric traits. ## Numeric Hierarchy -Current the Pony numerics hierarchy is as follows. +Current the Pony numeric type hierarchy is as follows: ```mermaid classDiagram @@ -63,7 +60,7 @@ SignedInteger <-- ISize SignedInteger <-- ILong ``` -This RFC introduces a few more numeric types: `Rational`, `Complex`, and arbitrary precision `BigInt`. This fit into the hiearchy in the following manner. +This RFC introduces four more numeric types: `Rational`, `BigInt`, `BigFloat`, and `Complex`. These fit into the numeric type hierarchy in the following manner: ```mermaid classDiagram @@ -110,130 +107,124 @@ SignedInteger <-- ISize SignedInteger <-- ILong ``` -## Structure - -I propose a structure of distinct subpackages including the following: - -+ `math/big`: Arbitrary precision numbers -+ `math/series`: Mathematical series -+ `math/constants`: Mathematical constants -+ `math/rational`: `Rational` data type and related functions -+ `math/complex`: `Complex` data type and related functions -+ `math/(x,exp,etc)`: experimental additions, utilities, and effective "catch-all" for matters that do not neatly fit into other subpackages - -## Common Data Types - -As previewed above in [Structure](#structure), expanding the `math` package should include implementations of common mathematics data types. Below are some implementation proposals for those data types. - -### `math/big` - -This is a package for arbitary precision numerics. - -Should include `BigInt`, `BigFloat`, and `BigDecimal` -- see [168](https://github.com/ponylang/rfcs/issues/168) - -### `math/series` - -`math/series` should include a `Series` trait which is a subclass of `Iterator`. The purpose of creating a new abstract data type is to generalize functions over mathematical series which do not make sense over iterators -- such as whether a `Series` is diverging or converging, a property that is all but meaningless for an `Iterator`. - -Example series include `Fibonacci` (already exists), `Pascal` (nCk), `Triangular` ({n+1}C{2}), `Square` (n^2), `Pentagonal` ({2n * (2n - 1)} / 2), etc. - -Current `math/Fibonacci` would go into this series package. - -### `math/constant` - -Initial values to include are those with underlying LLVM representations from the [numbers namespace](https://llvm.org/doxygen/namespacellvm_1_1numbers.html). - -Once these values exist in `math/constant`, they could be removed from where they are now, which is on `F32` and `F64` of [`Float`](https://github.com/ponylang/ponyc/blob/master/packages/builtin/float.pony). - -I foresee this as a primitive `Constant` with methods for each value (e.g., `Constant.pi[A: Float](): A(3.14159...)`). - -### `math/rational` - -`math/rational` should decide backing precision on `create[A: UnsignedInteger]` and return precision on `apply[B: Number]` -- `let x = Rational.create[U64](where numerator=2)` gives a type which is represented by `U64(2)`/`U64(2)` which can then be returned as any valid `Number`. `Rational` should be parameterized only on `UnsignedInteger` types and track sign via an internal field. +## Methods of Concern ```pony -// Parameterized on unsigned integers as negative status is tracked by field -class Rational[A: UnsignedInteger[A] val = USize] - var numerator: A - var denominator: A - var negative: Bool = false - - // Allow for creating Rationals from signed or unsigned integer arguments - new create[B: Integer[B] val = ISize](n: B, d: B = 1)? => - // Error if denominator is zero - if d == B.from[U8](0) then error end - - // Produce unsigned equivalents of arguments - // NOTE: this produces an error of form - // Error: - // main.pony:XX:YY: type argument is outside its constraint - // This is because Integer is not a subtype of UnsignedInteger - let n': A = if n < B.from[U8](0) then A.from[B](-n) else A.from[B](n) end - let n': A = if n < B.from[U8](0) then A.from[B](-n) else A.from[B](n) end - let d': A = if d < B.from[U8](0) then A.from[B](-d) else A.from[B](d) end - - // Find greatest common divisor and reduce fraction - let divisor: A = try GreatestCommonDivisor[A](n', d')? else A.from[U8](1) end - numerator = n' / divisor - denominator = d' / divisor - - // Set negative field if numerator, but not denominator is negative - if (n < B.from[U8](0)) and (B.from[U8](0) < d) then - negative = true - end - - fun string(): String => - // Size of 10 here is a placeholder for determining the size needed - let output = recover String(10) end - if negative then output.append("-") end - output.append(numerator.string()) - output.append(" / ") - output.append(denominator.string()) - output +trait val Real[A: Real[A] val] is + (Stringable & _ArithmeticConvertible & Comparable[A]) + ... + new val min_value() + new val max_value() + ... ``` -Changing the underlying precision "in-place" is done via a `prec[C: Real]` method which creates a new instance with a different precision. - -(I see this package as subsuming `Decimal` and `Fractional` types, as previously discussed in Zulip.) - -### `math/complex` - -`Complex` should follow similar to `math/rational` in that it is parameterized on `Real` types which are used to represent both the real and imaginary part of the number -- `Complex[U128](7, 2)` is `U128(7) + U128(2)i`. +`Real` will exist above `Rational`, `BigInt`, and `BigFloat` and as such would require defining the above methods for these types. `Rational` can be defined as minimum and maximum of the numerator, however `BigInt` and `BigFloat` by definition have arbitrary precision making defining a minimum and maximum difficult at the least -- if we define them as the minimum and maximum of a machine-sized int and float, or define them as -Inf and Inf -- or impossible at the worst -- if we define them by their possible limits which are arbitrary. -Changing the underlying precision is done via a `prec[C: Real]` method which creates a new instance of the value with a different precision. - -### `math/(x,exp,etc)` +```pony +trait val Integer[A: Integer[A] val] is Real[A] + ... + fun op_and(y: A): A => this and y + fun op_or(y: A): A => this or y + fun op_xor(y: A): A => this xor y + fun op_not(): A => not this + + fun bit_reverse(): A + """ + Reverse the order of the bits within the integer. + For example, 0b11101101 (237) would return 0b10110111 (183). + """ + + fun bswap(): A +``` -The name is subject to change and I want comments on what such a "catch all" package should be named to clearly denote it is for matters which do not neatly fit elsewhere. +`Integer` will exist above `BigInt` and as such would require defining the above methods -- however `BigInt` will be defined via other numerics so these methods could be applied recursively. -The explicit intention of this subpackage is to gather useful matters that do not fit into another more dedicated packages. Examples of matters that would be included here are trigonometry and linear algebra functions before corresponding `math/trig` and `math/la` packaged are made. +```pony +trait val FloatingPoint[A: FloatingPoint[A] val] is Real[A] + new val min_normalised() + new val epsilon() + fun tag radix(): U8 + fun tag precision2(): U8 + fun tag precision10(): U8 + fun tag min_exp2(): I16 + fun tag min_exp10(): I16 + fun tag max_exp2(): I16 + fun tag max_exp10(): I16 + ... + fun abs(): A + fun ceil(): A + fun floor(): A + fun round(): A + fun trunc(): A + + fun finite(): Bool + fun infinite(): Bool + fun nan(): Bool + + fun ldexp(x: A, exponent: I32): A + fun frexp(): (A, U32) + fun log(): A + fun log2(): A + fun log10(): A + fun logb(): A + + fun pow(y: A): A + fun powi(y: I32): A + + fun sqrt(): A + + fun sqrt_unsafe(): A + """ + Unsafe operation. + If this is negative, the result is undefined. + """ + + fun cbrt(): A + fun exp(): A + fun exp2(): A + + fun cos(): A + fun sin(): A + fun tan(): A + + fun cosh(): A + fun sinh(): A + fun tanh(): A + + fun acos(): A + fun asin(): A + fun atan(): A + fun atan2(y: A): A + + fun acosh(): A + fun asinh(): A + fun atanh(): A +``` -Current `math/IsPrime` would go into this catch-all package. +`FloatingPoint` will exist above `BigFloat` and as such would require defining the above methods -- many of which are ill-defined under arbitrary precision, or are functions using C-FFI and/or LLVM intrinsics. # How We Teach This -For functionality additions, ample documentation and usage examples within the expanded `math` library should be sufficient. - -I am proposing moving `Fibonacci` and `IsPrime` however nothing about existing functionality should change so teaching users about those updates should follow the pattern of providing example existing code with example modified code showing how to pick up the new locations of existing functionality. - -A new chapter should be added as Tutorial > Packages > Math which walks through the layout and usage of Pony `math`. +Adding ample documentation to these new numerics should suffice to teach Pony users how to leverage these types in their programs. I do not think any additions to the Pony Tutorial are needed, however if additions are desired than [Arithmetic](https://tutorial.ponylang.io/expressions/arithmetic.html) may be the most sensible location. # How We Test This I recommend use of `pony_check` to test all reversible operations pairs (`x+y-y == x`, `x*y/y == x`, etc), precision persistence (`Rational[U8](where numerator=x, denominator=y) * y == x`), and overflow/underflow protection (`Rational[U8](255, 1) + 1 => error`). -Testing `math` should not affect any other parts of Pony and as such standard CI should suffice. +Testing these numerics should not affect any other parts of Pony and as such standard CI should suffice. # Drawbacks -The major drawback is additional maintenance cost as well as immediate and continued disagreement around implementation and feature details. ++ Additional maintenance cost ++ May break existing code if methods must be removed from existing numeric traits to match the suggested hierarchy placements # Alternatives -The amount of subpackages is a lot and could be reduced down to one single `math` package if we so choose as none of the proposed additions clash at this time. +Alternatively, we can introduce these types in `math` as opposed to `builtin` and/or only introduce some of the proposed new numeric types. # Unresolved questions -+ How expansive should the `math` library become (whether that is one package or multiple subpackages)? -+ Do we need all of `Decimal`, `Rational`, and `Fractional` types? I am unaware of their distinction so believe adding a `Rational` (numerator and denominator) type to suffice for current needs. -+ Do we want the types here to be additions to `math` or, since they are really more numeric types, additions alongside other numeric types within `builtin`? ++ Should these types be introduced in `builtin` or in `math`? ++ Does `Rational` make sense as the "fractional type" or would we prefer `Fractional` to avoid confusion? ++ Do we want to also include a `Decimal` type? ++ How should we handle the stated "Methods of Concern"?