From 1cb3efb16ae6fe8328b1f1589e630824348b6060 Mon Sep 17 00:00:00 2001 From: Simone Robutti Date: Sat, 30 Mar 2019 19:52:44 +0100 Subject: [PATCH 1/8] added range_redesign rfc --- text/0000-range_redesign.md | 67 +++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 text/0000-range_redesign.md diff --git a/text/0000-range_redesign.md b/text/0000-range_redesign.md new file mode 100644 index 00000000..38aa37da --- /dev/null +++ b/text/0000-range_redesign.md @@ -0,0 +1,67 @@ +- Feature Name: range_redesign +- Start Date: 31/03/2019 +- RFC PR: (leave this empty) +- Pony Issue: (leave this empty) + +# Summary + +A Range reimplementation that includes the features from collections.Reverse together with a more comprehensive and coherent API. A reference implementation can be found here: https://github.com/chobeat/pony-range-proposal + +# Motivation + +The existence of Range and Reverse as separate classes, each one with their own limitations, to cover a use case that in most languages is addressed by a single API highlights the possibility for an improvement. Having a single, well-tested implementation to generate numeric ranges will improve the quality of life of new developers and possibly reduce unexpected behaviors for a core piece of the stdlib. +For example in the existing implementation it is possible to generate infinite ranges without any explicit control. + +In addition to that, the current implementation doesn't allow to express a range spanning over a whole data type since it makes assumptions on the exclusiveness of the right bound. + +# Detailed design + +The new implementation should fullfil the following design principles: + +* be able to generate all the possible ranges, increasing and decreasing, for all the data types, without incurring in overflow errors. +* offer a unified, usable, coherent API with the possibility to specify inclusive or exclusive bounds. To achieve this, the definition of the bounds and the range should be independent. The Range class should offer helper methods to cover the most common cases without explicitely instancing the bounds. +* never raise errors but instead return an empty list. This is done to make the class more usable in day to day usage. A version of the class with explicit errors can be considered. + +The reference implementation solves these problems in the following way: + +* implements a trait `Bound[T]`, implemented by two classes: `Inclusive[T]` and `Exclusive[T]` to represent the bounds. Each one of them will be able to return the value to be used to define the range in the case that they are used as an upper or lower bound. +* implements a class `Range` with a default constructor that respects the existing API +* implements a more flexible constructor with the following signature: ` + new define(b: Bound[T], e: Bound[T], step: T=1)`. +* Defines Step so that it cannot be negative or zero. In that case the range is considered empty. +**** Supports the notion of range direction. If b Date: Sun, 7 Apr 2019 13:06:45 +0200 Subject: [PATCH 2/8] added API layout --- text/0000-range_redesign.md | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/text/0000-range_redesign.md b/text/0000-range_redesign.md index 38aa37da..9cfcd834 100644 --- a/text/0000-range_redesign.md +++ b/text/0000-range_redesign.md @@ -10,9 +10,9 @@ A Range reimplementation that includes the features from collections.Reverse tog # Motivation The existence of Range and Reverse as separate classes, each one with their own limitations, to cover a use case that in most languages is addressed by a single API highlights the possibility for an improvement. Having a single, well-tested implementation to generate numeric ranges will improve the quality of life of new developers and possibly reduce unexpected behaviors for a core piece of the stdlib. -For example in the existing implementation it is possible to generate infinite ranges without any explicit control. +For example in the existing implementation it is possible to generate infinite ranges without any explicit control. To check that a range is infinite, the user has to call the dedicated function (`is_infinite`) to be sure that its range will terminate. This unloads onto the user a lot of unnecessary checks that could be incorporated into the Range class. In addition to that, the current implementation doesn't allow to express a range spanning over a whole data type since it makes assumptions on the exclusiveness of the right bound. This prevents the user from creating a range that includes the max element of a given data type. + -In addition to that, the current implementation doesn't allow to express a range spanning over a whole data type since it makes assumptions on the exclusiveness of the right bound. # Detailed design @@ -29,8 +29,28 @@ The reference implementation solves these problems in the following way: * implements a more flexible constructor with the following signature: ` new define(b: Bound[T], e: Bound[T], step: T=1)`. * Defines Step so that it cannot be negative or zero. In that case the range is considered empty. -**** Supports the notion of range direction. If b Date: Sun, 7 Apr 2019 15:53:40 +0200 Subject: [PATCH 3/8] improved drawbacks explanation --- text/0000-range_redesign.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-range_redesign.md b/text/0000-range_redesign.md index 9cfcd834..4a6e6c8a 100644 --- a/text/0000-range_redesign.md +++ b/text/0000-range_redesign.md @@ -68,9 +68,9 @@ The error management and the logic of the parameter `step` also deserve a dedica # Drawbacks -* Some behaviors might differ compared to the old Range implementation +* Some behaviors might differ compared to the old Range implementation. In particular, the range definitions that before would have created an infinite range, will now produce an empty range. * The "empty list error" logic instead of explicit errors might surprise a fraction of the users - +* If a range is increasing or decreasing will depend on the specific values of begin and end, making the "direction" of the range known only at runtime without explicit checks on these values. Nonetheless this behavior can be observed by the user using `is_forward`. # Alternatives From 7b2bf1b055db9bc0c0348d2ab1a4b18d7fd75fec Mon Sep 17 00:00:00 2001 From: Simone Robutti Date: Sun, 14 Apr 2019 10:53:47 +0200 Subject: [PATCH 4/8] added explanation for error handling --- text/0000-range_redesign.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-range_redesign.md b/text/0000-range_redesign.md index 4a6e6c8a..2a646271 100644 --- a/text/0000-range_redesign.md +++ b/text/0000-range_redesign.md @@ -20,7 +20,7 @@ The new implementation should fullfil the following design principles: * be able to generate all the possible ranges, increasing and decreasing, for all the data types, without incurring in overflow errors. * offer a unified, usable, coherent API with the possibility to specify inclusive or exclusive bounds. To achieve this, the definition of the bounds and the range should be independent. The Range class should offer helper methods to cover the most common cases without explicitely instancing the bounds. -* never raise errors but instead return an empty list. This is done to make the class more usable in day to day usage. A version of the class with explicit errors can be considered. +* never raise errors but instead return an empty list. This decision arises from the assumption that a majority of ranges are defined with bounds known at compile time while declaring ranges using runtime values is less common. The error handling in that case has to be done by calling the `is_invalid()` method. A version of the class with explicit errors can be considered. The reference implementation solves these problems in the following way: From c4e7115e6b96c45b67a98d48b272178553de3ebc Mon Sep 17 00:00:00 2001 From: Simone Robutti Date: Sun, 14 Apr 2019 11:40:45 +0200 Subject: [PATCH 5/8] added details to error logic --- text/0000-range_redesign.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/text/0000-range_redesign.md b/text/0000-range_redesign.md index 2a646271..beb48ded 100644 --- a/text/0000-range_redesign.md +++ b/text/0000-range_redesign.md @@ -26,8 +26,8 @@ The reference implementation solves these problems in the following way: * implements a trait `Bound[T]`, implemented by two classes: `Inclusive[T]` and `Exclusive[T]` to represent the bounds. Each one of them will be able to return the value to be used to define the range in the case that they are used as an upper or lower bound. * implements a class `Range` with a default constructor that respects the existing API -* implements a more flexible constructor with the following signature: ` - new define(b: Bound[T], e: Bound[T], step: T=1)`. +* implements a more flexible constructor with the following signature: + `new define(b: Bound[T], e: Bound[T], step: T=1)`. * Defines Step so that it cannot be negative or zero. In that case the range is considered empty. * Supports the notion of range direction. If b Date: Sun, 14 Apr 2019 12:46:16 +0200 Subject: [PATCH 6/8] explained floating point problems --- text/0000-range_redesign.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-range_redesign.md b/text/0000-range_redesign.md index beb48ded..71213779 100644 --- a/text/0000-range_redesign.md +++ b/text/0000-range_redesign.md @@ -85,4 +85,4 @@ The main alternative to the redesign as a whole is to keep the existing implemen # Unresolved questions -Some behaviors with float arithmetic are not really covered by the reference implementation and should be discussed. +In the reference implementation, the overflow check doesn't behave correctly for floating point numbers. It might very well be just an implementation mistake but given the trickyness of floating point arithmetics, it might hide some complexity that might reverberate in the specs too. From e097ff3b0081c2963d2a506b3b614600e40b40f5 Mon Sep 17 00:00:00 2001 From: Simone Robutti Date: Sun, 14 Apr 2019 13:24:23 +0200 Subject: [PATCH 7/8] rewrote "how we teach" --- text/0000-range_redesign.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-range_redesign.md b/text/0000-range_redesign.md index 71213779..2428cddd 100644 --- a/text/0000-range_redesign.md +++ b/text/0000-range_redesign.md @@ -54,9 +54,9 @@ class Range[T: (Real[T] val & Number) = USize] # How We Teach This -The API of the `Range` class is rather similar to the API of the `Range` implementations in many other languages. Explaining the basic behavior should be straightforward. The Bounds logic might deserve a dedicated explanation since it's necessary to use the default constructor and might be unexpected. +The API of the `Range` class is rather similar to the API of the `Range` implementations in many other languages. This should be documented in the stdlib documentation, explaining the basic usage alongside the details of the `Bounds` classes. On top of this, a dedicated example file can be produced, containing the intended usage of the API in scenarios where, for example, an error or an invalid range is produced. -The error management and the logic of the parameter `step` also deserve a dedicated explanation: while being similar to the logic seen in many other languages, in a quick survey we understood that the design of these behaviors have a much bigger variance and diversity across different languages. +In many languages you would also find explanations on how to create a range in every tutorial. Pony's tutorial is currently lacking coverage of the `collections` package but if and when it will be added, it should also contain an introduction to the `Range` class. # How We Test This From c9b648416b987cb87993e861045311f93c1a8db0 Mon Sep 17 00:00:00 2001 From: Simone Robutti Date: Sun, 14 Apr 2019 14:33:52 +0200 Subject: [PATCH 8/8] explained merge of Range and Reverse --- text/0000-range_redesign.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-range_redesign.md b/text/0000-range_redesign.md index 2428cddd..bf5d2018 100644 --- a/text/0000-range_redesign.md +++ b/text/0000-range_redesign.md @@ -12,7 +12,7 @@ A Range reimplementation that includes the features from collections.Reverse tog The existence of Range and Reverse as separate classes, each one with their own limitations, to cover a use case that in most languages is addressed by a single API highlights the possibility for an improvement. Having a single, well-tested implementation to generate numeric ranges will improve the quality of life of new developers and possibly reduce unexpected behaviors for a core piece of the stdlib. For example in the existing implementation it is possible to generate infinite ranges without any explicit control. To check that a range is infinite, the user has to call the dedicated function (`is_infinite`) to be sure that its range will terminate. This unloads onto the user a lot of unnecessary checks that could be incorporated into the Range class. In addition to that, the current implementation doesn't allow to express a range spanning over a whole data type since it makes assumptions on the exclusiveness of the right bound. This prevents the user from creating a range that includes the max element of a given data type. - +One major conceptual change is to make the direction of the Range more implicit in the API and determined by the runtime values of the Bounds. Before this was more explicit since there were two distinct classes to cover both use cases. The direction was unambigous but it was less intuitive for people coming from other languages where a split of the two concerns (increasing and decreasing ranges) is not so common. On top of this, in a scenario where both directions are valid, with the previous implementation it was required to involve two different classes and a check on the bounds to pass, while now this would be covered transparently. # Detailed design