From 06a12a91180e99a137d9e54c203d3747686cd8be Mon Sep 17 00:00:00 2001 From: Thomas Nield Date: Thu, 12 Jul 2018 15:53:35 -0500 Subject: [PATCH 01/18] Create exclusive-ranges.md --- proposals/stdlib/exclusive-ranges.md | 123 +++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 proposals/stdlib/exclusive-ranges.md diff --git a/proposals/stdlib/exclusive-ranges.md b/proposals/stdlib/exclusive-ranges.md new file mode 100644 index 000000000..664180246 --- /dev/null +++ b/proposals/stdlib/exclusive-ranges.md @@ -0,0 +1,123 @@ +# Proposal template for new API in the Standard Library + +This document provides a template that can be used to compose a proposal about new API in the Standard Library. + +# Title + +* **Type**: Standard Library API proposal +* **Author**: Thomas nield +* **Contributors**: (optional) contributor names, if any +* **Status**: Submitted +* **Prototype**: Not started / In progress / Implemented + + +## Summary + +Provide a brief description of the API proposed. + +## Similar API review + +* Is there a similar functionality in the standard library? +* How the same/similar concept is implemented in other languages/frameworks? + +## Use cases + +* Provide several *real-life* use cases (either links to public repositories or ad-hoc examples). + +## Alternatives + +* How verbose would be these use cases without the API proposed? + +## Dependencies + +What are the dependencies of the proposed API: + +* a subset of Kotlin Standard Library available on all supported platforms. +* JDK-specific dependencies, specify minimum JDK version. +* JS-specific dependencies. + +## Placement + +* Standard Library or one of kotlinx extension libraries +* package(s): should it be placed in one of packages imported by default? + +## Reference implementation + +* Provide the reference implementation and test cases. +In case if the API should be specialized for each primitive, only one reference implementation is enough. +* Provide the answers for the questions from the [Appendix](#appendix-questions-to-consider) in case they are not trivial. + +## Unresolved questions + +* List unresolved questions if any. +* Provide options to solve them. + +## Future advancements + +* What are the possible and most likely extension points? + + +------- + +# Appendix: Questions to consider +These questions are not a part of the proposal, +but be prepared to provide the answers if they aren't trivial. + +## Naming + +* Is it clear from name what API is for? +* Is it named consistently with other API with the similar purpose? +* Consider explorability of API via completion. + Generally we discourage introducing extensions imported by default for unconstrained generic type or `Any` type, as it pollutes the completion. + +Inspiring article on naming: http://blog.stephenwolfram.com/2010/10/the-poetry-of-function-naming/ + +## Contracts + +* What are the failure conditions and how are they handled? +* Whether the contracts (preconditions, invariants, exception handling) are consistent and are what they may be expected from the similar features. + +## Compatibility impact + +* How the proposal affects: + - source compatibility, + - binary compatibility (JVM), + - serialization compatibility (JVM)? +* Does it obsolete some other API? What deprecations and migrations are required? + +## Shape + +For new functions consider alternatives: + +* top-level or extension or member function +* function or property + +## Additional considerations for collection operations + +### Receiver types + +Consider if the operation could be provided not only for collections, +but for other collection-like receivers such as: + +* arrays +* sequences +* strings and char sequences +* maps +* ranges + +It is helpful to determine what are the collection requirements: + +* any iterable or sequence +* has size +* has fast indexed access +* has fast `contains` operation +* allows to mutate its elements +* allows to add/remove elements + +### Return type + +* Is the operation lazy or eager? Choose between `Sequence` and `List` +* What is the return type for each receiver type? +* Does the operation preserve the shape of the receiver? +I.e. returning `Sequence` for sequences and `List` for iterables. + From 4992d257b0dfd49073a4626dadd31a71dade9fbe Mon Sep 17 00:00:00 2001 From: Thomas Nield Date: Thu, 12 Jul 2018 19:35:05 -0500 Subject: [PATCH 02/18] Update exclusive-ranges.md --- proposals/stdlib/exclusive-ranges.md | 124 +++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) diff --git a/proposals/stdlib/exclusive-ranges.md b/proposals/stdlib/exclusive-ranges.md index 664180246..f90ffc81b 100644 --- a/proposals/stdlib/exclusive-ranges.md +++ b/proposals/stdlib/exclusive-ranges.md @@ -121,3 +121,127 @@ It is helpful to determine what are the collection requirements: * Does the operation preserve the shape of the receiver? I.e. returning `Sequence` for sequences and `List` for iterables. + + + + +```kotlin +infix fun Float.openRange(that: Float): OpenFloatingPointRange = OpenFloatRange(this, that) +infix fun Double.openRange(that: Double): OpenFloatingPointRange = OpenDoubleRange(this, that) + +fun ClosedRange.toOpenRange() = OpenDoubleRange(start,endInclusive) +fun ClosedRange.toOpenRange() = OpenFloatRange(start,endInclusive) + + +fun main(args: Array) { + + + val histogram = listOf( + (0.0..0.2).toOpenRange(), + (0.2..0.4).toOpenRange(), + (0.4..0.6).toOpenRange(), + (0.6..0.9).toOpenRange(), + (0.9..1.0) // uh oh + ) +} + + +public interface OpenRange> { + /** + * The minimum value in the range. + */ + public val start: T + + /** + * The maximum value in the range (inclusive). + */ + public val endExclusive: T + + /** + * Checks whether the specified [value] belongs to the range. + */ + public operator fun contains(value: T): Boolean = value >= start && value < endExclusive + + /** + * Checks whether the range is empty. + */ + public fun isEmpty(): Boolean = start > endExclusive +} + + +public interface OpenFloatingPointRange> : OpenRange { + override fun contains(value: T): Boolean = lessThan(start, value) && lessThan(value, endExclusive) + override fun isEmpty(): Boolean = !lessThan(start, endExclusive) + + /** + * Compares two values of range domain type and returns true if first is less than the second. + */ + fun lessThan(a: T, b: T): Boolean +} + + +/** + * An open range of values of type `Float`. + * + * Numbers are compared with the ends of this range according to IEEE-754. + */ +class OpenFloatRange( + start: Float, + endInclusive: Float +) : OpenFloatingPointRange { + private val _start = start + private val _endExclusive = endInclusive + override val start: Float get() = _start + override val endExclusive: Float get() = _endExclusive + + override fun lessThan(a: Float, b: Float): Boolean = a <= b + + override fun contains(value: Float): Boolean = value >= _start && value < _endExclusive + override fun isEmpty(): Boolean = !(_start <= _endExclusive) + + override fun equals(other: Any?): Boolean { + return other is OpenFloatRange && (isEmpty() && other.isEmpty() || + _start == other._start && _endExclusive == other._endExclusive) + } + + override fun hashCode(): Int { + return if (isEmpty()) -1 else 31 * _start.hashCode() + _endExclusive.hashCode() + } + + override fun toString(): String = "$_start..<$_endExclusive" +} + +/** + * An open range of values of type `Float`. + * + * Numbers are compared with the ends of this range according to IEEE-754. + */ +class OpenDoubleRange( + start: Double, + endInclusive: Double +) : OpenFloatingPointRange { + private val _start = start + private val _endExclusive = endInclusive + override val start: Double get() = _start + override val endExclusive: Double get() = _endExclusive + + override fun lessThan(a: Double, b: Double): Boolean = a <= b + + override fun contains(value: Double): Boolean = value >= _start && value < _endExclusive + override fun isEmpty(): Boolean = !(_start <= _endExclusive) + + override fun equals(other: Any?): Boolean { + return other is OpenDoubleRange && (isEmpty() && other.isEmpty() || + _start == other._start && _endExclusive == other._endExclusive) + } + + override fun hashCode(): Int { + return if (isEmpty()) -1 else 31 * _start.hashCode() + _endExclusive.hashCode() + } + + override fun toString(): String = "$_start..<$_endExclusive" +} + +``` + + From f6d5ba474555c75952798c784278b10d2e2b489f Mon Sep 17 00:00:00 2001 From: Thomas Nield Date: Fri, 13 Jul 2018 19:40:21 -0500 Subject: [PATCH 03/18] Add Exclusive Ranges Proposal --- proposals/stdlib/exclusive-ranges.md | 332 +++++++++++++++------------ 1 file changed, 179 insertions(+), 153 deletions(-) diff --git a/proposals/stdlib/exclusive-ranges.md b/proposals/stdlib/exclusive-ranges.md index f90ffc81b..e0d47513a 100644 --- a/proposals/stdlib/exclusive-ranges.md +++ b/proposals/stdlib/exclusive-ranges.md @@ -1,247 +1,273 @@ -# Proposal template for new API in the Standard Library +# Closed Ranges and Range Interface + -This document provides a template that can be used to compose a proposal about new API in the Standard Library. # Title * **Type**: Standard Library API proposal -* **Author**: Thomas nield -* **Contributors**: (optional) contributor names, if any +* **Author**: Thomas Nield +* **Contributors**: Thomas Nield * **Status**: Submitted -* **Prototype**: Not started / In progress / Implemented +* **Prototype**: Not started ## Summary -Provide a brief description of the API proposed. +After doing some substantial exploration using Kotlin for [statistics](https://github.com/thomasnield/kotlin-statistics) and [stochastic optimization](https://github.com/thomasnield/traveling_salesman_demo), I think there are opportunties to take advantage of a better implementation for ranges, and be able to support an `until` infix operator implementation for `Double` and `Float`. + +Kotlin's stdlib has an implementation for `ClosedRange`, but not `OpenRange`. I believe that the latter needs to be implemented at least for continuous `Double` and `Float` ranges, where the exclusive end point cannot be achieved discretely with a `ClosedRange`. + +The `ClosedRange` and `OpenRange` should also share a common `Range` parent, so the two can be mixed together in a `List` (i.e. [histograms](https://en.wikipedia.org/wiki/Histogram) or [probability density functions](https://en.wikipedia.org/wiki/Probability_density_function)) + ## Similar API review -* Is there a similar functionality in the standard library? -* How the same/similar concept is implemented in other languages/frameworks? -## Use cases +Kotlin's stdlib already contains `ClosedRange` implementations that can be invoked with a `..`, as in `0..10`. -* Provide several *real-life* use cases (either links to public repositories or ad-hoc examples). +Kotlin also indirectly supports an end-exclusive discrete range using a `ClosedRange`, and can be invoked with `until`, such as `0 until 10`. -## Alternatives +I believe that having an end-exclusive `until` implemented for `Double` and `Float` makes sense. However, an `OpenRange` will be needed to support the continuous nature of `Double` and `Float`. -* How verbose would be these use cases without the API proposed? +## Use cases -## Dependencies -What are the dependencies of the proposed API: +### Binning and Bucketing Continuous Ranges -* a subset of Kotlin Standard Library available on all supported platforms. -* JDK-specific dependencies, specify minimum JDK version. -* JS-specific dependencies. +[Discretization of continuous features](https://en.wikipedia.org/wiki/Discretization_of_continuous_features) is a common mathematical operation. This task comes up in mathematical modeling, statistics, probability, and machine learning. -## Placement -* Standard Library or one of kotlinx extension libraries -* package(s): should it be placed in one of packages imported by default? +For instance, I may bin `Sale` objects on their `price` into interval buckets of size `20.0`. -## Reference implementation +```kotlin +import java.time.LocalDate -* Provide the reference implementation and test cases. -In case if the API should be specialized for each primitive, only one reference implementation is enough. -* Provide the answers for the questions from the [Appendix](#appendix-questions-to-consider) in case they are not trivial. +fun main(args: Array) { -## Unresolved questions + data class Sale(val accountId: Int, val date: LocalDate, val price: Double) + + val sales = listOf( + Sale(1, LocalDate.of(2016,12,3), 180.0), + Sale(2, LocalDate.of(2016, 7, 4), 140.2), + Sale(3, LocalDate.of(2016, 6, 3), 111.4), + Sale(4, LocalDate.of(2016, 1, 5), 192.7), + Sale(5, LocalDate.of(2016, 5, 4), 137.9), + Sale(6, LocalDate.of(2016, 3, 6), 125.6), + Sale(7, LocalDate.of(2016, 12,4), 164.3), + Sale(8, LocalDate.of(2016, 7,11), 144.2) + ) + + //bin by double ranges + val binned = sales.binByDouble( + valueSelector = { it.price }, + binSize = 20.0, + rangeStart = 100.0 + ) + + val ranges = binned.ranges // should return endExclusive ranges + + ranges.forEach(::println) + + /* + OUTPUT: + 100..<120 + 120..<140 + 140..<160 + 160..<180 + 180..<200 + */ +} +``` -* List unresolved questions if any. -* Provide options to solve them. +I can also define my own ranges for a continuous histogram of values. I should also have the option of putting in a `ClosedRange` so the last bin can capture the final end boundary. -## Future advancements +```kotlin +val histogramBins = listOf( + 0.0 until 0.2, + 0.2 until 0.4, + 0.4 until 0.6, + 0.6 until 0.9, + 0.9..1.0 +) +``` -* What are the possible and most likely extension points? +To support a collection having both `ClosedRange` and `OpenRange` types, extracting a common `Range` interface might be necessary (with `contains()`, `isEmpty()`, `lowerBound` and `upperBound` functions and properties). -------- +### Probability and Weighted Sampling -# Appendix: Questions to consider -These questions are not a part of the proposal, -but be prepared to provide the answers if they aren't trivial. +Another use case is random sampling with a probability density function in some form. While it is unlikely the end/start of each continuous range will be selected in a random sampling, it is still not kosher for those points to be inclusive and overlap on each other. -## Naming -* Is it clear from name what API is for? -* Is it named consistently with other API with the similar purpose? -* Consider explorability of API via completion. - Generally we discourage introducing extensions imported by default for unconstrained generic type or `Any` type, as it pollutes the completion. +Below is an implementation of a `WeightedDice` that takes `T` sides with an associated probability. It uses an `OpenDoubleRange` with an `endExclusive`. While it is unlikely to happen with a `ClosedRange`, there is no chance a random `Double` will belong to two ranges because it falls on a border. -Inspiring article on naming: http://blog.stephenwolfram.com/2010/10/the-poetry-of-function-naming/ +Even if a `ClosedRange` was probabilistically acceptable, it is still misleading especially if those ranges are exposed via the API. -## Contracts +```kotlin +/** + * Assigns a probabilty to each distinct `T` item, and randomly selects `T` values given those probabilities. + * + * In other words, this is a Probability Density Function (PDF) for discrete `T` values + */ +class WeightedDice(val probabilities: Map) { -* What are the failure conditions and how are they handled? -* Whether the contracts (preconditions, invariants, exception handling) are consistent and are what they may be expected from the similar features. + constructor(vararg values: Pair): this( + values.toMap() + ) -## Compatibility impact + private val sum = probabilities.values.sum() -* How the proposal affects: - - source compatibility, - - binary compatibility (JVM), - - serialization compatibility (JVM)? -* Does it obsolete some other API? What deprecations and migrations are required? + val rangedDistribution = probabilities.let { -## Shape + var binStart = 0.0 -For new functions consider alternatives: + it.asSequence().sortedBy { it.value } + .map { it.key to OpenDoubleRange(binStart, it.value + binStart) } + .onEach { binStart = it.second.endExclusive } + .toMap() + } -* top-level or extension or member function -* function or property + /** + * Randomly selects a `T` value with probability + */ + fun roll() = ThreadLocalRandom.current().nextDouble(0.0, sum).let { + rangedDistribution.asIterable().first { rng -> it in rng.value }.key + } +} +``` -## Additional considerations for collection operations -### Receiver types -Consider if the operation could be provided not only for collections, -but for other collection-like receivers such as: +## Alternatives -* arrays -* sequences -* strings and char sequences -* maps -* ranges +The alternative would be for the Kotlin-Statistics library to continue making [its own `Range` implementations](https://github.com/thomasnield/kotlin-statistics/blob/master/src/main/kotlin/org/nield/kotlinstatistics/Ranges.kt). This is not desirable because this would make its ranges incompatible with stdlib's, and it does not get the language support with operators like `..` and `until`. -It is helpful to determine what are the collection requirements: -* any iterable or sequence -* has size -* has fast indexed access -* has fast `contains` operation -* allows to mutate its elements -* allows to add/remove elements +## Dependencies -### Return type +What are the dependencies of the proposed API: -* Is the operation lazy or eager? Choose between `Sequence` and `List` -* What is the return type for each receiver type? -* Does the operation preserve the shape of the receiver? -I.e. returning `Sequence` for sequences and `List` for iterables. +_None_ +## Placement + +* Standard Library or one of kotlinx extension libraries +* package(s): should it be placed in one of packages imported by default? +## Reference implementation +* Provide the reference implementation and test cases. +In case if the API should be specialized for each primitive, only one reference implementation is enough. +* Provide the answers for the questions from the [Appendix](#appendix-questions-to-consider) in case they are not trivial. +## Unresolved questions -```kotlin -infix fun Float.openRange(that: Float): OpenFloatingPointRange = OpenFloatRange(this, that) -infix fun Double.openRange(that: Double): OpenFloatingPointRange = OpenDoubleRange(this, that) +If we were to extract a `Range` parent for `ClosedRange` and `OpenRange`, what should it contain? -fun ClosedRange.toOpenRange() = OpenDoubleRange(start,endInclusive) -fun ClosedRange.toOpenRange() = OpenFloatRange(start,endInclusive) +Here is one proposed implementation: a `lowerBound` and `upperBound` should be defined to generalize the `start` and `end`, but not indicate whether they are inclusive or exclusive. This allows a `List` to still have access to the start and end values, regardless if they are inclusive or exclusive. +```kotlin +/** + * A `Range` is an abstract interface common to all ranges, regardless of their inclusive or exclusive nature + */ +public interface Range> { -fun main(args: Array) { + /** + * The minimum value in the range, regardless if it is inclusive or exclusive + */ + public val lowerBound: T + /** + * The maximum value in the range, regardless if it is inclusive or exclusive + */ + public val upperBound: T + /** + * Checks whether the specified [value] belongs to the range. + */ + public operator fun contains(value: T): Boolean - val histogram = listOf( - (0.0..0.2).toOpenRange(), - (0.2..0.4).toOpenRange(), - (0.4..0.6).toOpenRange(), - (0.6..0.9).toOpenRange(), - (0.9..1.0) // uh oh - ) + /** + * Checks whether the range is empty. + */ + public fun isEmpty(): Boolean } +``` + +The child implementations can still have their own properties such as `start`, `endInclusive`, `endExclusive` (and later `startExclusive`) but they should have the same values as their respective `lowerBound` and `upperBound` properties. -public interface OpenRange> { + +```kotlin +public interface OpenRange>: Range { /** * The minimum value in the range. */ - public val start: T + public val start: T get() = lowerBound /** * The maximum value in the range (inclusive). */ - public val endExclusive: T + public val endExclusive: T get() = upperBound /** * Checks whether the specified [value] belongs to the range. */ - public operator fun contains(value: T): Boolean = value >= start && value < endExclusive + override operator fun contains(value: T): Boolean = value >= start && value < endExclusive /** * Checks whether the range is empty. */ - public fun isEmpty(): Boolean = start > endExclusive + override fun isEmpty(): Boolean = start > endExclusive } -public interface OpenFloatingPointRange> : OpenRange { - override fun contains(value: T): Boolean = lessThan(start, value) && lessThan(value, endExclusive) - override fun isEmpty(): Boolean = !lessThan(start, endExclusive) +public interface ClosedRange>: Range { /** - * Compares two values of range domain type and returns true if first is less than the second. + * The minimum value in the range. */ - fun lessThan(a: T, b: T): Boolean -} - + public val start: T get() = lowerBound -/** - * An open range of values of type `Float`. - * - * Numbers are compared with the ends of this range according to IEEE-754. - */ -class OpenFloatRange( - start: Float, - endInclusive: Float -) : OpenFloatingPointRange { - private val _start = start - private val _endExclusive = endInclusive - override val start: Float get() = _start - override val endExclusive: Float get() = _endExclusive - - override fun lessThan(a: Float, b: Float): Boolean = a <= b - - override fun contains(value: Float): Boolean = value >= _start && value < _endExclusive - override fun isEmpty(): Boolean = !(_start <= _endExclusive) - - override fun equals(other: Any?): Boolean { - return other is OpenFloatRange && (isEmpty() && other.isEmpty() || - _start == other._start && _endExclusive == other._endExclusive) - } + /** + * The maximum value in the range (inclusive). + */ + public val endInclusive: T get() = upperBound - override fun hashCode(): Int { - return if (isEmpty()) -1 else 31 * _start.hashCode() + _endExclusive.hashCode() - } + /** + * Checks whether the specified [value] belongs to the range. + */ + override operator fun contains(value: T): Boolean = value >= start && value <= endInclusive - override fun toString(): String = "$_start..<$_endExclusive" + /** + * Checks whether the range is empty. + */ + override fun isEmpty(): Boolean = start > endInclusive } -/** - * An open range of values of type `Float`. - * - * Numbers are compared with the ends of this range according to IEEE-754. - */ -class OpenDoubleRange( - start: Double, - endInclusive: Double -) : OpenFloatingPointRange { - private val _start = start - private val _endExclusive = endInclusive - override val start: Double get() = _start - override val endExclusive: Double get() = _endExclusive - - override fun lessThan(a: Double, b: Double): Boolean = a <= b - - override fun contains(value: Double): Boolean = value >= _start && value < _endExclusive - override fun isEmpty(): Boolean = !(_start <= _endExclusive) - - override fun equals(other: Any?): Boolean { - return other is OpenDoubleRange && (isEmpty() && other.isEmpty() || - _start == other._start && _endExclusive == other._endExclusive) - } - override fun hashCode(): Int { - return if (isEmpty()) -1 else 31 * _start.hashCode() + _endExclusive.hashCode() - } - override fun toString(): String = "$_start..<$_endExclusive" -} + ``` +## Future advancements + +This should leave an open opportunity to create an `ExclusiveRange` later, with `startExclusive` and `endExclusive` properties. + +Introducing `ExclusiveRange` might raise the concern the `start` property of `ClosedRange` and `OpenRange` implies inclusivity, and perhaps should explicitly be named `startInclusive`. + + +------- + + +## Naming + +Considering there is a `ClosedRange` already with an `endInclusive`, it makes sense to call a property with an `endExclusive` to oppositely be called an `OpenRange`. + +## Contracts + +For the `OpenRange` implementations, the `lowerBound`/`start` must be less than the `upperBound`/`endExclusive`. Otherwise the range should be empty. + +## Compatibility impact +This might cause a large (but hopefully non-breaking) change of all `ClosedRange` implementations, as the new parent to `ClosedRange` and all its children will be `Range`. From f6cd84d846bde8652b4ef6dbccbd1c4e8e961834 Mon Sep 17 00:00:00 2001 From: Thomas Nield Date: Fri, 13 Jul 2018 19:43:06 -0500 Subject: [PATCH 04/18] Update exclusive-ranges.md --- proposals/stdlib/exclusive-ranges.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/proposals/stdlib/exclusive-ranges.md b/proposals/stdlib/exclusive-ranges.md index e0d47513a..2a4a2a7e3 100644 --- a/proposals/stdlib/exclusive-ranges.md +++ b/proposals/stdlib/exclusive-ranges.md @@ -151,14 +151,12 @@ _None_ ## Placement -* Standard Library or one of kotlinx extension libraries -* package(s): should it be placed in one of packages imported by default? +package kotlin.ranges ## Reference implementation -* Provide the reference implementation and test cases. -In case if the API should be specialized for each primitive, only one reference implementation is enough. -* Provide the answers for the questions from the [Appendix](#appendix-questions-to-consider) in case they are not trivial. +You can find an `OpenRange` implementation [here in Kotlin-Statistics](https://github.com/thomasnield/kotlin-statistics/blob/master/src/main/kotlin/org/nield/kotlinstatistics/Ranges.kt). There is no `Range` parent however. + ## Unresolved questions From 834e66d9b0a90dcf7d0d4a9c9b883998c7be2918 Mon Sep 17 00:00:00 2001 From: Thomas Nield Date: Fri, 13 Jul 2018 19:50:44 -0500 Subject: [PATCH 05/18] Update exclusive-ranges.md --- proposals/stdlib/exclusive-ranges.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/proposals/stdlib/exclusive-ranges.md b/proposals/stdlib/exclusive-ranges.md index 2a4a2a7e3..e84bd5ea8 100644 --- a/proposals/stdlib/exclusive-ranges.md +++ b/proposals/stdlib/exclusive-ranges.md @@ -1,9 +1,6 @@ # Closed Ranges and Range Interface - -# Title - * **Type**: Standard Library API proposal * **Author**: Thomas Nield * **Contributors**: Thomas Nield From 78ef580273604f97024bdbe09bedefc3fd33665a Mon Sep 17 00:00:00 2001 From: Thomas Nield Date: Fri, 13 Jul 2018 20:00:03 -0500 Subject: [PATCH 06/18] Update exclusive-ranges.md --- proposals/stdlib/exclusive-ranges.md | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/proposals/stdlib/exclusive-ranges.md b/proposals/stdlib/exclusive-ranges.md index e84bd5ea8..a5932647d 100644 --- a/proposals/stdlib/exclusive-ranges.md +++ b/proposals/stdlib/exclusive-ranges.md @@ -20,7 +20,7 @@ The `ClosedRange` and `OpenRange` should also share a common `Range` parent, so ## Similar API review -Kotlin's stdlib already contains `ClosedRange` implementations that can be invoked with a `..`, as in `0..10`. +Kotlin's stdlib already contains `ClosedRange` implementations that can be invoked with a `..` as in `0..10`. Kotlin also indirectly supports an end-exclusive discrete range using a `ClosedRange`, and can be invoked with `until`, such as `0 until 10`. @@ -31,7 +31,7 @@ I believe that having an end-exclusive `until` implemented for `Double` and `Flo ### Binning and Bucketing Continuous Ranges -[Discretization of continuous features](https://en.wikipedia.org/wiki/Discretization_of_continuous_features) is a common mathematical operation. This task comes up in mathematical modeling, statistics, probability, and machine learning. +[Discretization of continuous features](https://en.wikipedia.org/wiki/Discretization_of_continuous_features) is a common mathematical operation. This task comes up in mathematical modeling, basic statistics, probability, and machine learning. For instance, I may bin `Sale` objects on their `price` into interval buckets of size `20.0`. @@ -61,7 +61,7 @@ fun main(args: Array) { rangeStart = 100.0 ) - val ranges = binned.ranges // should return endExclusive ranges +val ranges = binned.ranges // should return endExclusive ranges ranges.forEach(::println) @@ -76,7 +76,7 @@ fun main(args: Array) { } ``` -I can also define my own ranges for a continuous histogram of values. I should also have the option of putting in a `ClosedRange` so the last bin can capture the final end boundary. +I can also define my own ranges for a continuous histogram of values. I should have the option of putting in a `ClosedRange` so the last bin can capture the final end boundary. ```kotlin val histogramBins = listOf( @@ -88,17 +88,14 @@ val histogramBins = listOf( ) ``` -To support a collection having both `ClosedRange` and `OpenRange` types, extracting a common `Range` interface might be necessary (with `contains()`, `isEmpty()`, `lowerBound` and `upperBound` functions and properties). +To support a collection having both `ClosedRange` and `OpenRange` types, extracting a common `Range` interface might be necessary (with `contains()`, `isEmpty()`, `lowerBound`, and `upperBound` functions and properties). ### Probability and Weighted Sampling Another use case is random sampling with a probability density function in some form. While it is unlikely the end/start of each continuous range will be selected in a random sampling, it is still not kosher for those points to be inclusive and overlap on each other. - -Below is an implementation of a `WeightedDice` that takes `T` sides with an associated probability. It uses an `OpenDoubleRange` with an `endExclusive`. While it is unlikely to happen with a `ClosedRange`, there is no chance a random `Double` will belong to two ranges because it falls on a border. - -Even if a `ClosedRange` was probabilistically acceptable, it is still misleading especially if those ranges are exposed via the API. +Below is an implementation of a `WeightedDice` that takes `T` sides with an associated probability. It uses an `OpenDoubleRange` with an `endExclusive`. While it might be probabilistically negligible, there is no chance a random `Double` will belong to two ranges because it falls on a border. Also, even if a `ClosedRange` is not doing damage to fair sampling, it is still misleading especially if those ranges are exposed via the API. ```kotlin /** @@ -137,7 +134,7 @@ class WeightedDice(val probabilities: Map) { ## Alternatives -The alternative would be for the Kotlin-Statistics library to continue making [its own `Range` implementations](https://github.com/thomasnield/kotlin-statistics/blob/master/src/main/kotlin/org/nield/kotlinstatistics/Ranges.kt). This is not desirable because this would make its ranges incompatible with stdlib's, and it does not get the language support with operators like `..` and `until`. +The alternative would be for the [Kotlin-Statistics](https://github.com/thomasnield/kotlin-statistics) library to continue making [its own `Range` implementations](https://github.com/thomasnield/kotlin-statistics/blob/master/src/main/kotlin/org/nield/kotlinstatistics/Ranges.kt). This is not desirable because this would make its ranges incompatible with stdlib's, and it does not get the language support with operators like `..` and `until`. ## Dependencies @@ -257,11 +254,11 @@ Introducing `ExclusiveRange` might raise the concern the `start` property of `Cl ## Naming -Considering there is a `ClosedRange` already with an `endInclusive`, it makes sense to call a property with an `endExclusive` to oppositely be called an `OpenRange`. +Considering there is a `ClosedRange` already with an `endInclusive`, it makes sense to call a range with an `endExclusive` to oppositely be called an `OpenRange`. ## Contracts -For the `OpenRange` implementations, the `lowerBound`/`start` must be less than the `upperBound`/`endExclusive`. Otherwise the range should be empty. +For the `OpenRange` implementations, the `lowerBound`/`start` must be *less than* the `upperBound`/`endExclusive`. Otherwise the range should be empty. ## Compatibility impact From a25a7ab53c8e82aa97f0af721c2d4b6af9ac6ac0 Mon Sep 17 00:00:00 2001 From: Thomas Nield Date: Sat, 14 Jul 2018 08:52:32 -0500 Subject: [PATCH 07/18] Update exclusive-ranges.md --- proposals/stdlib/exclusive-ranges.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/stdlib/exclusive-ranges.md b/proposals/stdlib/exclusive-ranges.md index a5932647d..2c65f8b56 100644 --- a/proposals/stdlib/exclusive-ranges.md +++ b/proposals/stdlib/exclusive-ranges.md @@ -1,4 +1,4 @@ -# Closed Ranges and Range Interface +# Exclusive Ranges and Range Interface * **Type**: Standard Library API proposal From fdebbe6ac22a86776de00160afee1eb494b3947f Mon Sep 17 00:00:00 2001 From: Thomas Nield Date: Sun, 15 Jul 2018 15:39:39 -0500 Subject: [PATCH 08/18] Update exclusive-ranges.md --- proposals/stdlib/exclusive-ranges.md | 42 +++++++++++++++------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/proposals/stdlib/exclusive-ranges.md b/proposals/stdlib/exclusive-ranges.md index 2c65f8b56..1ea6e2587 100644 --- a/proposals/stdlib/exclusive-ranges.md +++ b/proposals/stdlib/exclusive-ranges.md @@ -3,7 +3,7 @@ * **Type**: Standard Library API proposal * **Author**: Thomas Nield -* **Contributors**: Thomas Nield +* **Contributors**: Thomas Nield, Burkhard Mittelbach * **Status**: Submitted * **Prototype**: Not started @@ -12,9 +12,11 @@ After doing some substantial exploration using Kotlin for [statistics](https://github.com/thomasnield/kotlin-statistics) and [stochastic optimization](https://github.com/thomasnield/traveling_salesman_demo), I think there are opportunties to take advantage of a better implementation for ranges, and be able to support an `until` infix operator implementation for `Double` and `Float`. -Kotlin's stdlib has an implementation for `ClosedRange`, but not `OpenRange`. I believe that the latter needs to be implemented at least for continuous `Double` and `Float` ranges, where the exclusive end point cannot be achieved discretely with a `ClosedRange`. +Kotlin's stdlib has an implementation for `ClosedRange`, but not `OpenRange`, `OpenStartRange`, and `OpenEndRange`. I believe that latter items need to be implemented at least for continuous `Double` and `Float` ranges, where the exclusive start/end point cannot be achieved discretely with a `ClosedRange`. -The `ClosedRange` and `OpenRange` should also share a common `Range` parent, so the two can be mixed together in a `List` (i.e. [histograms](https://en.wikipedia.org/wiki/Histogram) or [probability density functions](https://en.wikipedia.org/wiki/Probability_density_function)) +The `ClosedRange`, `OpenRange`, `OpenStartRange`, and `OpenEndRange` should also share a common `Range` parent, so they all can be mixed together in a `List` (i.e. [histograms](https://en.wikipedia.org/wiki/Histogram) or [probability density functions](https://en.wikipedia.org/wiki/Probability_density_function)). + +Also proposed is deprecating the `start` property in `ClosedRange` for `startInclusive`. The `Range` interface would also benefit from having `upperBound` and `lowerBound` properties that do not define a specific inclusivity/exclusivity behavior. ## Similar API review @@ -24,7 +26,7 @@ Kotlin's stdlib already contains `ClosedRange` implementations that can be invok Kotlin also indirectly supports an end-exclusive discrete range using a `ClosedRange`, and can be invoked with `until`, such as `0 until 10`. -I believe that having an end-exclusive `until` implemented for `Double` and `Float` makes sense. However, an `OpenRange` will be needed to support the continuous nature of `Double` and `Float`. +I believe that having an end-exclusive `until` implemented for `Double` and `Float` makes sense. However, `OpenRange`, `OpenStartRange`, and `OpenEndRange` will be needed to support the continuous nature of `Double` and `Float`. ## Use cases @@ -34,7 +36,7 @@ I believe that having an end-exclusive `until` implemented for `Double` and `Flo [Discretization of continuous features](https://en.wikipedia.org/wiki/Discretization_of_continuous_features) is a common mathematical operation. This task comes up in mathematical modeling, basic statistics, probability, and machine learning. -For instance, I may bin `Sale` objects on their `price` into interval buckets of size `20.0`. +For instance, I may bin `Sale` objects on their `price` into interval buckets of size `20.0`. Putting an `until` between two `Double` or `Float` values will return an `OpenEndRange`. ```kotlin import java.time.LocalDate @@ -88,14 +90,14 @@ val histogramBins = listOf( ) ``` -To support a collection having both `ClosedRange` and `OpenRange` types, extracting a common `Range` interface might be necessary (with `contains()`, `isEmpty()`, `lowerBound`, and `upperBound` functions and properties). +To support a collection having both `ClosedRange` and `OpenEndRange` types, extracting a common `Range` interface might be necessary (with `contains()`, `isEmpty()`, `lowerBound`, and `upperBound` functions and properties). ### Probability and Weighted Sampling Another use case is random sampling with a probability density function in some form. While it is unlikely the end/start of each continuous range will be selected in a random sampling, it is still not kosher for those points to be inclusive and overlap on each other. -Below is an implementation of a `WeightedDice` that takes `T` sides with an associated probability. It uses an `OpenDoubleRange` with an `endExclusive`. While it might be probabilistically negligible, there is no chance a random `Double` will belong to two ranges because it falls on a border. Also, even if a `ClosedRange` is not doing damage to fair sampling, it is still misleading especially if those ranges are exposed via the API. +Below is an implementation of a `WeightedDice` that takes `T` sides with an associated probability. It uses an `OpenEndDoubleRange` with an `endExclusive`. While it might be probabilistically negligible, there is no chance a random `Double` will belong to two ranges because it falls on a border. Also, even if a `ClosedRange` is not doing damage to fair sampling, it is still misleading especially if those ranges are exposed via the API. ```kotlin /** @@ -149,12 +151,12 @@ package kotlin.ranges ## Reference implementation -You can find an `OpenRange` implementation [here in Kotlin-Statistics](https://github.com/thomasnield/kotlin-statistics/blob/master/src/main/kotlin/org/nield/kotlinstatistics/Ranges.kt). There is no `Range` parent however. +You can find an `OpenEndRange` implementation [here in Kotlin-Statistics](https://github.com/thomasnield/kotlin-statistics/blob/master/src/main/kotlin/org/nield/kotlinstatistics/Ranges.kt), although there it is called an `OpenRange`. There is no `Range` parent either. ## Unresolved questions -If we were to extract a `Range` parent for `ClosedRange` and `OpenRange`, what should it contain? +If we were to extract a `Range` parent for `ClosedRange`, `OpenRange`, `OpenStartRange`, and `OpenEndRange`, what should it contain? Here is one proposed implementation: a `lowerBound` and `upperBound` should be defined to generalize the `start` and `end`, but not indicate whether they are inclusive or exclusive. This allows a `List` to still have access to the start and end values, regardless if they are inclusive or exclusive. @@ -186,15 +188,15 @@ public interface Range> { ``` -The child implementations can still have their own properties such as `start`, `endInclusive`, `endExclusive` (and later `startExclusive`) but they should have the same values as their respective `lowerBound` and `upperBound` properties. +The child implementations can still have their own properties such as `startInclusive` `endExclusive`, `endInclusive`, `endExclusive` (and later `startExclusive`) but they should have the same values as their respective `lowerBound` and `upperBound` properties. This also begs the question if `start` should be deprecated and explicitly be labeled `startInclusive` or `startExclusive`. ```kotlin -public interface OpenRange>: Range { +public interface OpenEndRange>: Range { /** * The minimum value in the range. */ - public val start: T get() = lowerBound + public val startInclusive: T get() = lowerBound /** * The maximum value in the range (inclusive). @@ -218,7 +220,7 @@ public interface ClosedRange>: Range { /** * The minimum value in the range. */ - public val start: T get() = lowerBound + public val startInclusive: T get() = lowerBound /** * The maximum value in the range (inclusive). @@ -237,16 +239,13 @@ public interface ClosedRange>: Range { } - - - ``` ## Future advancements -This should leave an open opportunity to create an `ExclusiveRange` later, with `startExclusive` and `endExclusive` properties. +The `Range` interface maybe can have additional properties describing inclusivity/exclusivity, such as `isStartInclusive`. This may add clutter so we should consider this augmentation carefully. -Introducing `ExclusiveRange` might raise the concern the `start` property of `ClosedRange` and `OpenRange` implies inclusivity, and perhaps should explicitly be named `startInclusive`. +It might also be beneficial to explore progressions with continuous numeric types, so expressions like `10.0 until 2.0 step 0.5` can be used. This can have a broad range of use cases, including [temperature schedules for simulated annealing](https://github.com/thomasnield/traveling_salesman_demo/blob/master/src/main/kotlin/Model.kt#L290-L292). ------- @@ -254,11 +253,14 @@ Introducing `ExclusiveRange` might raise the concern the `start` property of `Cl ## Naming -Considering there is a `ClosedRange` already with an `endInclusive`, it makes sense to call a range with an `endExclusive` to oppositely be called an `OpenRange`. +Considering there is a `ClosedRange`, the additional types `OpenRange`, `OpenEndRange`, and `OpenStartRange` follow a stdlib-like convention. ## Contracts -For the `OpenRange` implementations, the `lowerBound`/`start` must be *less than* the `upperBound`/`endExclusive`. Otherwise the range should be empty. +For the `OpenRange` and `OpenEndRange` implementations, the `lowerBound` must be *less than* the `upperBound`. Otherwise the range should be empty. + +For the `OpenStartRange` implementations, the `lowerBound` must be *less than or equal to* the `upperBound`. Otherwise the range should be empty. + ## Compatibility impact From 980a65850ac3df42dd9bd334d86612b240197b35 Mon Sep 17 00:00:00 2001 From: Thomas Nield Date: Sun, 15 Jul 2018 15:49:24 -0500 Subject: [PATCH 09/18] Update exclusive-ranges.md --- proposals/stdlib/exclusive-ranges.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/proposals/stdlib/exclusive-ranges.md b/proposals/stdlib/exclusive-ranges.md index 1ea6e2587..f27d0ba9d 100644 --- a/proposals/stdlib/exclusive-ranges.md +++ b/proposals/stdlib/exclusive-ranges.md @@ -12,7 +12,7 @@ After doing some substantial exploration using Kotlin for [statistics](https://github.com/thomasnield/kotlin-statistics) and [stochastic optimization](https://github.com/thomasnield/traveling_salesman_demo), I think there are opportunties to take advantage of a better implementation for ranges, and be able to support an `until` infix operator implementation for `Double` and `Float`. -Kotlin's stdlib has an implementation for `ClosedRange`, but not `OpenRange`, `OpenStartRange`, and `OpenEndRange`. I believe that latter items need to be implemented at least for continuous `Double` and `Float` ranges, where the exclusive start/end point cannot be achieved discretely with a `ClosedRange`. +Kotlin's stdlib has an implementation for `ClosedRange`, but not `OpenRange`, `OpenStartRange`, and `OpenEndRange`. I believe the latter items need to be implemented at least for continuous `Double` and `Float` ranges, where the exclusive start/end point cannot be achieved discretely with a `ClosedRange`. The `ClosedRange`, `OpenRange`, `OpenStartRange`, and `OpenEndRange` should also share a common `Range` parent, so they all can be mixed together in a `List` (i.e. [histograms](https://en.wikipedia.org/wiki/Histogram) or [probability density functions](https://en.wikipedia.org/wiki/Probability_density_function)). @@ -158,7 +158,7 @@ You can find an `OpenEndRange` implementation [here in Kotlin-Statistics](https: If we were to extract a `Range` parent for `ClosedRange`, `OpenRange`, `OpenStartRange`, and `OpenEndRange`, what should it contain? -Here is one proposed implementation: a `lowerBound` and `upperBound` should be defined to generalize the `start` and `end`, but not indicate whether they are inclusive or exclusive. This allows a `List` to still have access to the start and end values, regardless if they are inclusive or exclusive. +Here is one proposed implementation: a `lowerBound` and `upperBound` should be defined to generalize the start and end, but not indicate whether they are inclusive or exclusive. This allows a `List` to still have access to the start and end values, regardless if they are inclusive or exclusive. ```kotlin /** @@ -188,7 +188,7 @@ public interface Range> { ``` -The child implementations can still have their own properties such as `startInclusive` `endExclusive`, `endInclusive`, `endExclusive` (and later `startExclusive`) but they should have the same values as their respective `lowerBound` and `upperBound` properties. This also begs the question if `start` should be deprecated and explicitly be labeled `startInclusive` or `startExclusive`. +The child implementations can still have their own properties such as `startInclusive` `endExclusive`, `endInclusive`, `endExclusive`, and `startExclusive`, but they should have the same values as their respective `lowerBound` and `upperBound` counterparts. This also begs the question if `start` should be deprecated and explicitly be labeled `startInclusive` or `startExclusive`. ```kotlin From 7e776c86d23eba9a2a23b257f44508476e54630a Mon Sep 17 00:00:00 2001 From: Thomas Nield Date: Sun, 15 Jul 2018 15:51:14 -0500 Subject: [PATCH 10/18] Update exclusive-ranges.md --- proposals/stdlib/exclusive-ranges.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/stdlib/exclusive-ranges.md b/proposals/stdlib/exclusive-ranges.md index f27d0ba9d..1006c7e45 100644 --- a/proposals/stdlib/exclusive-ranges.md +++ b/proposals/stdlib/exclusive-ranges.md @@ -264,4 +264,4 @@ For the `OpenStartRange` implementations, the `lowerBound` must be *less than o ## Compatibility impact -This might cause a large (but hopefully non-breaking) change of all `ClosedRange` implementations, as the new parent to `ClosedRange` and all its children will be `Range`. +The usage of `start` in `ClosedRange` may need to be deprecated in favor of explicit `startInclusive` and `startExclusive`. From 76be0e830f7279c57ccf2699fc96623156862947 Mon Sep 17 00:00:00 2001 From: Thomas Nield Date: Sun, 15 Jul 2018 23:11:43 -0500 Subject: [PATCH 11/18] Update exclusive-ranges.md --- proposals/stdlib/exclusive-ranges.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/proposals/stdlib/exclusive-ranges.md b/proposals/stdlib/exclusive-ranges.md index 1006c7e45..50280a64d 100644 --- a/proposals/stdlib/exclusive-ranges.md +++ b/proposals/stdlib/exclusive-ranges.md @@ -36,7 +36,7 @@ I believe that having an end-exclusive `until` implemented for `Double` and `Flo [Discretization of continuous features](https://en.wikipedia.org/wiki/Discretization_of_continuous_features) is a common mathematical operation. This task comes up in mathematical modeling, basic statistics, probability, and machine learning. -For instance, I may bin `Sale` objects on their `price` into interval buckets of size `20.0`. Putting an `until` between two `Double` or `Float` values will return an `OpenEndRange`. +For instance, I may bin `Sale` objects on their `price` into interval buckets of size `20.0`. ```kotlin import java.time.LocalDate @@ -78,7 +78,7 @@ val ranges = binned.ranges // should return endExclusive ranges } ``` -I can also define my own ranges for a continuous histogram of values. I should have the option of putting in a `ClosedRange` so the last bin can capture the final end boundary. +I can also define my own ranges for a continuous histogram of values, and putting an `until` between two `Double` or `Float` values will conveniently return an `OpenEndRange`. I should have the option of putting in a `ClosedRange` so the last bin can capture the final end boundary. ```kotlin val histogramBins = listOf( @@ -118,7 +118,7 @@ class WeightedDice(val probabilities: Map) { var binStart = 0.0 it.asSequence().sortedBy { it.value } - .map { it.key to OpenDoubleRange(binStart, it.value + binStart) } + .map { it.key to (binStart until (it.value + binStart)) } .onEach { binStart = it.second.endExclusive } .toMap() } From 17048e948c11b7e941a1d54c426903cfba8b30de Mon Sep 17 00:00:00 2001 From: Thomas Nield Date: Sun, 22 Jul 2018 08:04:30 -0500 Subject: [PATCH 12/18] Update exclusive-ranges.md --- proposals/stdlib/exclusive-ranges.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/proposals/stdlib/exclusive-ranges.md b/proposals/stdlib/exclusive-ranges.md index 50280a64d..e55a5075d 100644 --- a/proposals/stdlib/exclusive-ranges.md +++ b/proposals/stdlib/exclusive-ranges.md @@ -28,6 +28,12 @@ Kotlin also indirectly supports an end-exclusive discrete range using a `ClosedR I believe that having an end-exclusive `until` implemented for `Double` and `Float` makes sense. However, `OpenRange`, `OpenStartRange`, and `OpenEndRange` will be needed to support the continuous nature of `Double` and `Float`. +Here are some other comparable API's on other platforms: + +[Google Guava](https://github.com/google/guava/wiki/RangesExplained)- This is probably the best source for inspiration, and has several interesting ideas for further range functionalities down the road. + +[Python](https://www.pythoncentral.io/pythons-range-function-explained/) - Surprisingly, Python does not support float ranges but makes it easy to implement yourself via generators. + ## Use cases From 733fd5743a180b9f46b4910e14003309539db84a Mon Sep 17 00:00:00 2001 From: Thomas Nield Date: Sun, 22 Jul 2018 08:08:14 -0500 Subject: [PATCH 13/18] Update exclusive-ranges.md --- proposals/stdlib/exclusive-ranges.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/proposals/stdlib/exclusive-ranges.md b/proposals/stdlib/exclusive-ranges.md index e55a5075d..982416571 100644 --- a/proposals/stdlib/exclusive-ranges.md +++ b/proposals/stdlib/exclusive-ranges.md @@ -21,19 +21,14 @@ Also proposed is deprecating the `start` property in `ClosedRange` for `startInc ## Similar API review - -Kotlin's stdlib already contains `ClosedRange` implementations that can be invoked with a `..` as in `0..10`. - -Kotlin also indirectly supports an end-exclusive discrete range using a `ClosedRange`, and can be invoked with `until`, such as `0 until 10`. - -I believe that having an end-exclusive `until` implemented for `Double` and `Float` makes sense. However, `OpenRange`, `OpenStartRange`, and `OpenEndRange` will be needed to support the continuous nature of `Double` and `Float`. - Here are some other comparable API's on other platforms: [Google Guava](https://github.com/google/guava/wiki/RangesExplained)- This is probably the best source for inspiration, and has several interesting ideas for further range functionalities down the road. [Python](https://www.pythoncentral.io/pythons-range-function-explained/) - Surprisingly, Python does not support float ranges but makes it easy to implement yourself via generators. +[R](http://www.endmemo.com/program/R/seq.php) - R supports ranges as sequencies in a similiar manner as Kotlin progressions, but with float/double support. + ## Use cases From 931dd6f838289ecd829815e1583f523e3c71ac8f Mon Sep 17 00:00:00 2001 From: Thomas Nield Date: Sun, 22 Jul 2018 08:08:33 -0500 Subject: [PATCH 14/18] Update exclusive-ranges.md --- proposals/stdlib/exclusive-ranges.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/stdlib/exclusive-ranges.md b/proposals/stdlib/exclusive-ranges.md index 982416571..24cd6ec8a 100644 --- a/proposals/stdlib/exclusive-ranges.md +++ b/proposals/stdlib/exclusive-ranges.md @@ -27,7 +27,7 @@ Here are some other comparable API's on other platforms: [Python](https://www.pythoncentral.io/pythons-range-function-explained/) - Surprisingly, Python does not support float ranges but makes it easy to implement yourself via generators. -[R](http://www.endmemo.com/program/R/seq.php) - R supports ranges as sequencies in a similiar manner as Kotlin progressions, but with float/double support. +[R](http://www.endmemo.com/program/R/seq.php) - Supports ranges as sequencies in a similiar manner as Kotlin progressions, but with float/double support. ## Use cases From b487faa9dfb7d76b0c4d1badfff9c252281d7242 Mon Sep 17 00:00:00 2001 From: Thomas Nield Date: Tue, 14 Aug 2018 19:49:00 -0500 Subject: [PATCH 15/18] Update exclusive-ranges.md --- proposals/stdlib/exclusive-ranges.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/stdlib/exclusive-ranges.md b/proposals/stdlib/exclusive-ranges.md index 24cd6ec8a..7746d9c25 100644 --- a/proposals/stdlib/exclusive-ranges.md +++ b/proposals/stdlib/exclusive-ranges.md @@ -200,7 +200,7 @@ public interface OpenEndRange>: Range { public val startInclusive: T get() = lowerBound /** - * The maximum value in the range (inclusive). + * The maximum value in the range (exclusive). */ public val endExclusive: T get() = upperBound From 4c7cd207d1239230b0f2d84514bbf9a7f70a00ab Mon Sep 17 00:00:00 2001 From: Thomas Nield Date: Thu, 16 Aug 2018 11:24:42 -0500 Subject: [PATCH 16/18] Update exclusive-ranges.md --- proposals/stdlib/exclusive-ranges.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/stdlib/exclusive-ranges.md b/proposals/stdlib/exclusive-ranges.md index 7746d9c25..ed5c434f6 100644 --- a/proposals/stdlib/exclusive-ranges.md +++ b/proposals/stdlib/exclusive-ranges.md @@ -246,7 +246,7 @@ public interface ClosedRange>: Range { The `Range` interface maybe can have additional properties describing inclusivity/exclusivity, such as `isStartInclusive`. This may add clutter so we should consider this augmentation carefully. -It might also be beneficial to explore progressions with continuous numeric types, so expressions like `10.0 until 2.0 step 0.5` can be used. This can have a broad range of use cases, including [temperature schedules for simulated annealing](https://github.com/thomasnield/traveling_salesman_demo/blob/master/src/main/kotlin/Model.kt#L290-L292). +It might also be beneficial to explore progressions with continuous numeric types, so expressions like `10.0 downTo 2.0 step 0.5` can be used. This can have a broad range of use cases, including [temperature schedules for simulated annealing](https://github.com/thomasnield/traveling_salesman_demo/blob/master/src/main/kotlin/Model.kt#L290-L292). ------- From e590c705b479e31052a950810110dc607d2dc52f Mon Sep 17 00:00:00 2001 From: Thomas Nield Date: Wed, 26 Dec 2018 20:49:01 -0600 Subject: [PATCH 17/18] Rename OpenStartRange -> OpenClosed Range, OpenEndRange -> ClosedOpenRange --- proposals/stdlib/exclusive-ranges.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/proposals/stdlib/exclusive-ranges.md b/proposals/stdlib/exclusive-ranges.md index ed5c434f6..b906ca924 100644 --- a/proposals/stdlib/exclusive-ranges.md +++ b/proposals/stdlib/exclusive-ranges.md @@ -12,9 +12,9 @@ After doing some substantial exploration using Kotlin for [statistics](https://github.com/thomasnield/kotlin-statistics) and [stochastic optimization](https://github.com/thomasnield/traveling_salesman_demo), I think there are opportunties to take advantage of a better implementation for ranges, and be able to support an `until` infix operator implementation for `Double` and `Float`. -Kotlin's stdlib has an implementation for `ClosedRange`, but not `OpenRange`, `OpenStartRange`, and `OpenEndRange`. I believe the latter items need to be implemented at least for continuous `Double` and `Float` ranges, where the exclusive start/end point cannot be achieved discretely with a `ClosedRange`. +Kotlin's stdlib has an implementation for `ClosedRange`, but not `OpenRange`, `OpenClosedRange`, and `ClosedOpenRange`. I believe the latter items need to be implemented at least for continuous `Double` and `Float` ranges, where the exclusive start/end point cannot be achieved discretely with a `ClosedRange`. -The `ClosedRange`, `OpenRange`, `OpenStartRange`, and `OpenEndRange` should also share a common `Range` parent, so they all can be mixed together in a `List` (i.e. [histograms](https://en.wikipedia.org/wiki/Histogram) or [probability density functions](https://en.wikipedia.org/wiki/Probability_density_function)). +The `ClosedRange`, `OpenRange`, `OpenClosedRange`, and `ClosedOpenRange` should also share a common `Range` parent, so they all can be mixed together in a `List` (i.e. [histograms](https://en.wikipedia.org/wiki/Histogram) or [probability density functions](https://en.wikipedia.org/wiki/Probability_density_function)). Also proposed is deprecating the `start` property in `ClosedRange` for `startInclusive`. The `Range` interface would also benefit from having `upperBound` and `lowerBound` properties that do not define a specific inclusivity/exclusivity behavior. @@ -79,7 +79,7 @@ val ranges = binned.ranges // should return endExclusive ranges } ``` -I can also define my own ranges for a continuous histogram of values, and putting an `until` between two `Double` or `Float` values will conveniently return an `OpenEndRange`. I should have the option of putting in a `ClosedRange` so the last bin can capture the final end boundary. +I can also define my own ranges for a continuous histogram of values, and putting an `until` between two `Double` or `Float` values will conveniently return an `ClosedOpenRange`. I should have the option of putting in a `ClosedRange` so the last bin can capture the final end boundary. ```kotlin val histogramBins = listOf( @@ -91,7 +91,7 @@ val histogramBins = listOf( ) ``` -To support a collection having both `ClosedRange` and `OpenEndRange` types, extracting a common `Range` interface might be necessary (with `contains()`, `isEmpty()`, `lowerBound`, and `upperBound` functions and properties). +To support a collection having both `ClosedRange` and `ClosedOpenRange` types, extracting a common `Range` interface might be necessary (with `contains()`, `isEmpty()`, `lowerBound`, and `upperBound` functions and properties). ### Probability and Weighted Sampling @@ -152,12 +152,12 @@ package kotlin.ranges ## Reference implementation -You can find an `OpenEndRange` implementation [here in Kotlin-Statistics](https://github.com/thomasnield/kotlin-statistics/blob/master/src/main/kotlin/org/nield/kotlinstatistics/Ranges.kt), although there it is called an `OpenRange`. There is no `Range` parent either. +You can find an `ClosedOpenRange` implementation [here in Kotlin-Statistics](https://github.com/thomasnield/kotlin-statistics/blob/master/src/main/kotlin/org/nield/kotlinstatistics/Ranges.kt), although there it is called an `OpenRange`. There is no `Range` parent either. ## Unresolved questions -If we were to extract a `Range` parent for `ClosedRange`, `OpenRange`, `OpenStartRange`, and `OpenEndRange`, what should it contain? +If we were to extract a `Range` parent for `ClosedRange`, `OpenRange`, `OpenClosedRange`, and `ClosedOpenRange`, what should it contain? Here is one proposed implementation: a `lowerBound` and `upperBound` should be defined to generalize the start and end, but not indicate whether they are inclusive or exclusive. This allows a `List` to still have access to the start and end values, regardless if they are inclusive or exclusive. @@ -193,7 +193,7 @@ The child implementations can still have their own properties such as `startIncl ```kotlin -public interface OpenEndRange>: Range { +public interface ClosedOpenRange>: Range { /** * The minimum value in the range. */ @@ -254,13 +254,13 @@ It might also be beneficial to explore progressions with continuous numeric type ## Naming -Considering there is a `ClosedRange`, the additional types `OpenRange`, `OpenEndRange`, and `OpenStartRange` follow a stdlib-like convention. +Considering there is a `ClosedRange`, the additional types `OpenRange`, `ClosedOpenRange`, and `OpenClosedRange` follow a stdlib-like convention. ## Contracts -For the `OpenRange` and `OpenEndRange` implementations, the `lowerBound` must be *less than* the `upperBound`. Otherwise the range should be empty. +For the `OpenRange` and `ClosedOpenRange` implementations, the `lowerBound` must be *less than* the `upperBound`. Otherwise the range should be empty. -For the `OpenStartRange` implementations, the `lowerBound` must be *less than or equal to* the `upperBound`. Otherwise the range should be empty. +For the `OpenClosedRange` implementations, the `lowerBound` must be *less than or equal to* the `upperBound`. Otherwise the range should be empty. ## Compatibility impact From 3a72cf68902fafdfd7aabfce3e668ce294119d3b Mon Sep 17 00:00:00 2001 From: Thomas Nield Date: Thu, 27 Dec 2018 08:00:11 -0600 Subject: [PATCH 18/18] Update exclusive-ranges.md --- proposals/stdlib/exclusive-ranges.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/stdlib/exclusive-ranges.md b/proposals/stdlib/exclusive-ranges.md index b906ca924..8e25bcc5a 100644 --- a/proposals/stdlib/exclusive-ranges.md +++ b/proposals/stdlib/exclusive-ranges.md @@ -152,7 +152,7 @@ package kotlin.ranges ## Reference implementation -You can find an `ClosedOpenRange` implementation [here in Kotlin-Statistics](https://github.com/thomasnield/kotlin-statistics/blob/master/src/main/kotlin/org/nield/kotlinstatistics/Ranges.kt), although there it is called an `OpenRange`. There is no `Range` parent either. +You can find implementations [here in Kotlin-Statistics](https://github.com/thomasnield/kotlin-statistics/tree/master/src/main/kotlin/org/nield/kotlinstatistics/range). ## Unresolved questions