Add a `clamp` function to Algorithm.swift


(James Froggatt) #1

This topic caught my attention. I support the idea, I'm currently using an extension for this.

Should “16.clamped(to: 0..<10)” produce 9 or 10?

9

Sounds good.

What about “16.clamped(to: 0..<0)”, which is an empty range?

For `Int`? Crash (which, until about 5 minutes ago, is what I thought would happen if you tried to create a range that’s empty like that). For types that support it, I’d say NaN or something like “nil”/“empty” is the most appropriate return value

Nasty but reasonable. I'd support it returning nil in this case, this would provide a warning that the result may not be a valid number, as well as providing this elegant range validation:

`if let index = candidate.clamped(to: array.indices) { … }`

(or a possible alternative spelling:)

`if let index = array.indices.clamp(candidate) { … }`

Does “16.0.clamped(to: 0..<10)” yield 10.0 or the next-smaller representable Double?

Next-smaller, IMHO. It’s not exactly semantically correct, but AFAIK that’s as correct as Float/Double can be.

One could argue the most ‘correct’ value here is the closest representation of the theoretical value, `10.0 - (1 / ∞)`, which should clearly round to 10. However, this also rounds the result back out of the range, meaning it's unsuitable as a result. Both possibilities are, for lack of a better word, ugly.

Mostly though I’d really like to be able to clamp to array indices, which are pretty much always written as a `Range`, rather than a `ClosedRange`. We could write the function for `Range` to only be generic over `Comparable&Integer`, if the floating point corner cases are too much.

- Dave Sweeris

My conclusion also. I'd like to see this added to the standard library, if it's in scope for Swift 4.

Sidenote: I can't help but think index validation would be better solved in many cases by an optional-returning array subscript (`array[ifPresent: index]`), but I've seen this solution turned down several times due to the lack of discoverability (read: lack of Xcode autocompletion, which I originally thought was a bug until it stayed that way for ~3 years). I'd also like to see this feature get added in some form, eventually.


(Xiaodi Wu) #2

Some days ago, Ben Cohen laid out the criteria for helper functions in the
Standard Library. Here's some of his very enlightening text and the six
criteria:

The operation needs to carry its weight. Even once we have ABI stability,

so the size of the std lib becomes less of a concern as it could ship as
part of the OS, we still need to keep helper method growth under control.
APIs bristling with methods like an over-decorated Xmas tree are bad for
usability. As mentioned in the String manifesto, String+Foundation
currently has over 200 methods/properties. Helpers are no good if you can’t
find them to use them.

1. Is it truly a frequent operation?
2. Is the helper more readable? Is the composed equivalent obvious at a
glance?
3. Does the helper have the flexibility to cover all common cases?
4. Is there a correctness trap with the composed equivalent? Is there a
correctness trap with the helper?
5. Is there a performance trap with the composed equivalent? Or with the
helper?
6. Does the helper actually encourage misuse?

The reasons I'm opposed to adding `clamp` are as follows:

It is trivially composed from `min` and `max`, with no correctness traps.

As the discussion above shows, there are correctness traps when you have a
`clamp` operation that takes open ranges, whereas the composed form using
`min` and `max` does not suffer from the same issue.

It encourages misuse, because Dave's desired use case (for indices) works
*only* for arrays and falls down for collections. This is similar to the
problem which motivates removal of `enumerated()` as discussed in other
threads. In this case, it is not guaranteed that a collection with indices
`0..<10` has an index 9.

···

On Fri, Mar 10, 2017 at 4:48 PM, James Froggatt via swift-evolution < swift-evolution@swift.org> wrote:

This topic caught my attention. I support the idea, I'm currently using an
extension for this.

>>Should “16.clamped(to: 0..<10)” produce 9 or 10?

>9

Sounds good.

>>What about “16.clamped(to: 0..<0)”, which is an empty range?

>For `Int`? Crash (which, until about 5 minutes ago, is what I thought
would happen if you tried to create a range that’s empty like that). For
types that support it, I’d say NaN or something like “nil”/“empty” is the
most appropriate return value

Nasty but reasonable. I'd support it returning nil in this case, this
would provide a warning that the result may not be a valid number, as well
as providing this elegant range validation:

`if let index = candidate.clamped(to: array.indices) { … }`

(or a possible alternative spelling:)

`if let index = array.indices.clamp(candidate) { … }`

>>Does “16.0.clamped(to: 0..<10)” yield 10.0 or the next-smaller
representable Double?

>Next-smaller, IMHO. It’s not exactly semantically correct, but AFAIK
that’s as correct as Float/Double can be.

One could argue the most ‘correct’ value here is the closest
representation of the theoretical value, `10.0 - (1 / ∞)`, which should
clearly round to 10. However, this also rounds the result back out of the
range, meaning it's unsuitable as a result. Both possibilities are, for
lack of a better word, ugly.

>Mostly though I’d really like to be able to clamp to array indices, which
are pretty much always written as a `Range`, rather than a `ClosedRange`.
We could write the function for `Range` to only be generic over
`Comparable&Integer`, if the floating point corner cases are too much.

> - Dave Sweeris

My conclusion also. I'd like to see this added to the standard library, if
it's in scope for Swift 4.

Sidenote: I can't help but think index validation would be better solved
in many cases by an optional-returning array subscript (`array[ifPresent:
index]`), but I've seen this solution turned down several times due to the
lack of discoverability (read: lack of Xcode autocompletion, which I
originally thought was a bug until it stayed that way for ~3 years). I'd
also like to see this feature get added in some form, eventually.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(James Froggatt) #3

Some days ago, Ben Cohen laid out the criteria for helper functions in the Standard Library. Here's some of his very enlightening text and the six criteria:

The operation needs to carry its weight. Even once we have ABI stability, so the size of the std lib becomes less of a concern as it could ship as part of the OS, we still need to keep helper method growth under control. APIs bristling with methods like an over-decorated Xmas tree are bad for usability. As mentioned in the String manifesto, String+Foundation currently has over 200 methods/properties. Helpers are no good if you can’t find them to use them.

1. Is it truly a frequent operation?
2. Is the helper more readable? Is the composed equivalent obvious at a glance?
3. Does the helper have the flexibility to cover all common cases?
4. Is there a correctness trap with the composed equivalent? Is there a correctness trap with the helper?
5. Is there a performance trap with the composed equivalent? Or with the helper?
6. Does the helper actually encourage misuse?

The reasons I'm opposed to adding `clamp` are as follows:

It is trivially composed from `min` and `max`, with no correctness traps.

As the discussion above shows, there are correctness traps when you have a `clamp` operation that takes open ranges, whereas the composed form using `min` and `max` does not suffer from the same issue.

It encourages misuse, because Dave's desired use case (for indices) works *only* for arrays and falls down for collections. This is similar to the problem which motivates removal of `enumerated()` as discussed in other threads. In this case, it is not guaranteed that a collection with indices `0..<10` has an index 9.

You make a good point, but then how exactly did the range-clamping function make it into the standard library in the first place? I can't think of frequent reason to want to clamp a range to within another range putting my mind to it, yet a clamp function on the Bound type has uses with arrays and offers a clear improvement to readability. Then there's the (potential) correctness trap of mixing up min and max, which I find leads me to need to double-check the logic after typing.

Seeing those criteria just makes it all the more frustrating that the range-clamping version is the one to have made the cut.

···

On 11 Mar 2017, at 00:05, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Fri, Mar 10, 2017 at 4:48 PM, James Froggatt via swift-evolution <swift-evolution@swift.org> wrote:

This topic caught my attention. I support the idea, I'm currently using an extension for this.

>>Should “16.clamped(to: 0..<10)” produce 9 or 10?

>9

Sounds good.

>>What about “16.clamped(to: 0..<0)”, which is an empty range?

>For `Int`? Crash (which, until about 5 minutes ago, is what I thought would happen if you tried to create a range that’s empty like that). For types that support it, I’d say NaN or something like “nil”/“empty” is the most appropriate return value

Nasty but reasonable. I'd support it returning nil in this case, this would provide a warning that the result may not be a valid number, as well as providing this elegant range validation:

`if let index = candidate.clamped(to: array.indices) { … }`

(or a possible alternative spelling:)

`if let index = array.indices.clamp(candidate) { … }`

>>Does “16.0.clamped(to: 0..<10)” yield 10.0 or the next-smaller representable Double?

>Next-smaller, IMHO. It’s not exactly semantically correct, but AFAIK that’s as correct as Float/Double can be.

One could argue the most ‘correct’ value here is the closest representation of the theoretical value, `10.0 - (1 / ∞)`, which should clearly round to 10. However, this also rounds the result back out of the range, meaning it's unsuitable as a result. Both possibilities are, for lack of a better word, ugly.

>Mostly though I’d really like to be able to clamp to array indices, which are pretty much always written as a `Range`, rather than a `ClosedRange`. We could write the function for `Range` to only be generic over `Comparable&Integer`, if the floating point corner cases are too much.

> - Dave Sweeris

My conclusion also. I'd like to see this added to the standard library, if it's in scope for Swift 4.

Sidenote: I can't help but think index validation would be better solved in many cases by an optional-returning array subscript (`array[ifPresent: index]`), but I've seen this solution turned down several times due to the lack of discoverability (read: lack of Xcode autocompletion, which I originally thought was a bug until it stayed that way for ~3 years). I'd also like to see this feature get added in some form, eventually.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(James Froggatt) #4

Some days ago, Ben Cohen laid out the criteria for helper functions in the Standard Library. Here's some of his very enlightening text and the six criteria:

The operation needs to carry its weight. Even once we have ABI stability, so the size of the std lib becomes less of a concern as it could ship as part of the OS, we still need to keep helper method growth under control. APIs bristling with methods like an over-decorated Xmas tree are bad for usability. As mentioned in the String manifesto, String+Foundation currently has over 200 methods/properties. Helpers are no good if you can’t find them to use them.

1. Is it truly a frequent operation?
2. Is the helper more readable? Is the composed equivalent obvious at a glance?
3. Does the helper have the flexibility to cover all common cases?
4. Is there a correctness trap with the composed equivalent? Is there a correctness trap with the helper?
5. Is there a performance trap with the composed equivalent? Or with the helper?
6. Does the helper actually encourage misuse?

The reasons I'm opposed to adding `clamp` are as follows:

It is trivially composed from `min` and `max`, with no correctness traps.

As the discussion above shows, there are correctness traps when you have a `clamp` operation that takes open ranges, whereas the composed form using `min` and `max` does not suffer from the same issue.

It encourages misuse, because Dave's desired use case (for indices) works *only* for arrays and falls down for collections. This is similar to the problem which motivates removal of `enumerated()` as discussed in other threads. In this case, it is not guaranteed that a collection with indices `0..<10` has an index 9.

You make a good point, but then how exactly did the range-clamping function make it into the standard library in the first place? I can't think of frequent reason to want to clamp a range to within another range putting my mind to it, yet a clamp function on the Bound type has uses with arrays and offers a clear improvement to readability. Then there's the (potential) correctness trap of mixing up min and max, which I find leads me to need to double-check the logic after typing.

Seeing those criteria just makes it all the more frustrating that the range-clamping version is the one to have made the cut.

Rereading, you're point is that the range-clamping version does solve a correctness trap (and that the Bound version does not?). Could you explain how you reached to this conclusion?

···

On 11 Mar 2017, at 00:21, James Froggatt <james.froggatt@me.com> wrote:

On 11 Mar 2017, at 00:05, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Fri, Mar 10, 2017 at 4:48 PM, James Froggatt via swift-evolution <swift-evolution@swift.org> wrote:

This topic caught my attention. I support the idea, I'm currently using an extension for this.

>>Should “16.clamped(to: 0..<10)” produce 9 or 10?

>9

Sounds good.

>>What about “16.clamped(to: 0..<0)”, which is an empty range?

>For `Int`? Crash (which, until about 5 minutes ago, is what I thought would happen if you tried to create a range that’s empty like that). For types that support it, I’d say NaN or something like “nil”/“empty” is the most appropriate return value

Nasty but reasonable. I'd support it returning nil in this case, this would provide a warning that the result may not be a valid number, as well as providing this elegant range validation:

`if let index = candidate.clamped(to: array.indices) { … }`

(or a possible alternative spelling:)

`if let index = array.indices.clamp(candidate) { … }`

>>Does “16.0.clamped(to: 0..<10)” yield 10.0 or the next-smaller representable Double?

>Next-smaller, IMHO. It’s not exactly semantically correct, but AFAIK that’s as correct as Float/Double can be.

One could argue the most ‘correct’ value here is the closest representation of the theoretical value, `10.0 - (1 / ∞)`, which should clearly round to 10. However, this also rounds the result back out of the range, meaning it's unsuitable as a result. Both possibilities are, for lack of a better word, ugly.

>Mostly though I’d really like to be able to clamp to array indices, which are pretty much always written as a `Range`, rather than a `ClosedRange`. We could write the function for `Range` to only be generic over `Comparable&Integer`, if the floating point corner cases are too much.

> - Dave Sweeris

My conclusion also. I'd like to see this added to the standard library, if it's in scope for Swift 4.

Sidenote: I can't help but think index validation would be better solved in many cases by an optional-returning array subscript (`array[ifPresent: index]`), but I've seen this solution turned down several times due to the lack of discoverability (read: lack of Xcode autocompletion, which I originally thought was a bug until it stayed that way for ~3 years). I'd also like to see this feature get added in some form, eventually.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Xiaodi Wu) #5

Some days ago, Ben Cohen laid out the criteria for helper functions in the
Standard Library. Here's some of his very enlightening text and the six
criteria:

The operation needs to carry its weight. Even once we have ABI stability,

so the size of the std lib becomes less of a concern as it could ship as
part of the OS, we still need to keep helper method growth under control.
APIs bristling with methods like an over-decorated Xmas tree are bad for
usability. As mentioned in the String manifesto, String+Foundation
currently has over 200 methods/properties. Helpers are no good if you can’t
find them to use them.

1. Is it truly a frequent operation?
2. Is the helper more readable? Is the composed equivalent obvious at a
glance?
3. Does the helper have the flexibility to cover all common cases?
4. Is there a correctness trap with the composed equivalent? Is there a
correctness trap with the helper?
5. Is there a performance trap with the composed equivalent? Or with the
helper?
6. Does the helper actually encourage misuse?

The reasons I'm opposed to adding `clamp` are as follows:

It is trivially composed from `min` and `max`, with no correctness traps.

As the discussion above shows, there are correctness traps when you have a
`clamp` operation that takes open ranges, whereas the composed form using
`min` and `max` does not suffer from the same issue.

It encourages misuse, because Dave's desired use case (for indices) works
*only* for arrays and falls down for collections. This is similar to the
problem which motivates removal of `enumerated()` as discussed in other
threads. In this case, it is not guaranteed that a collection with indices
`0..<10` has an index 9.

You make a good point, but then how exactly did the range-clamping
function make it into the standard library in the first place? I can't
think of frequent reason to want to clamp a range to within another range
putting my mind to it, yet a clamp function on the Bound type has uses with
arrays and offers a clear improvement to readability. Then there's the
(potential) correctness trap of mixing up min and max, which I find leads
me to need to double-check the logic after typing.

Seeing those criteria just makes it all the more frustrating that the
range-clamping version is the one to have made the cut.

Rereading, you're point is that the range-clamping version does solve a
correctness trap (and that the Bound version does not?). Could you explain
how you reached to this conclusion?

I'm not making that point. I'm just pointing out that these are the current
criteria for expanding the standard library.

If, looking back, you feel that the existing `clamped(to:)` doesn't fit the
criteria, then you can propose its removal. However, there are also
standards for changing existing APIs in Swift 4. The first one is that "the
existing syntax/API being changed must be actively harmful"--which is a bar
that `clamped(to:)` doesn't meet, IMO.

···

On Fri, Mar 10, 2017 at 6:29 PM, James Froggatt <james.froggatt@me.com> wrote:

On 11 Mar 2017, at 00:21, James Froggatt <james.froggatt@me.com> wrote:
On 11 Mar 2017, at 00:05, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Fri, Mar 10, 2017 at 4:48 PM, James Froggatt via swift-evolution < > swift-evolution@swift.org> wrote:

This topic caught my attention. I support the idea, I'm currently using
an extension for this.

>>Should “16.clamped(to: 0..<10)” produce 9 or 10?

>9

Sounds good.

>>What about “16.clamped(to: 0..<0)”, which is an empty range?

>For `Int`? Crash (which, until about 5 minutes ago, is what I thought
would happen if you tried to create a range that’s empty like that). For
types that support it, I’d say NaN or something like “nil”/“empty” is the
most appropriate return value

Nasty but reasonable. I'd support it returning nil in this case, this
would provide a warning that the result may not be a valid number, as well
as providing this elegant range validation:

`if let index = candidate.clamped(to: array.indices) { … }`

(or a possible alternative spelling:)

`if let index = array.indices.clamp(candidate) { … }`

>>Does “16.0.clamped(to: 0..<10)” yield 10.0 or the next-smaller
representable Double?

>Next-smaller, IMHO. It’s not exactly semantically correct, but AFAIK
that’s as correct as Float/Double can be.

One could argue the most ‘correct’ value here is the closest
representation of the theoretical value, `10.0 - (1 / ∞)`, which should
clearly round to 10. However, this also rounds the result back out of the
range, meaning it's unsuitable as a result. Both possibilities are, for
lack of a better word, ugly.

>Mostly though I’d really like to be able to clamp to array indices,
which are pretty much always written as a `Range`, rather than a
`ClosedRange`. We could write the function for `Range` to only be generic
over `Comparable&Integer`, if the floating point corner cases are too much.

> - Dave Sweeris

My conclusion also. I'd like to see this added to the standard library,
if it's in scope for Swift 4.

Sidenote: I can't help but think index validation would be better solved
in many cases by an optional-returning array subscript (`array[ifPresent:
index]`), but I've seen this solution turned down several times due to the
lack of discoverability (read: lack of Xcode autocompletion, which I
originally thought was a bug until it stayed that way for ~3 years). I'd
also like to see this feature get added in some form, eventually.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Nicholas Maccharoli) #6

I want to thank everyone so much for the feedback.

I really would like to propose a draft of this in the near future given
that
from reading this thread it seems that a good amount of people
seem to want a `clamp(to:)` function of some kind but are still not settled
on the exact implementation details.

To help keep the discussion moving forward I thought having this starting
draft of the
proposal up would help:

https://github.com/Nirma/swift-evolution/commit/a2d0c32c775704ad02fe406e6365bada7875c788

Please if you have a chance feel free to comment or add to it, after it
becomes a little more mature I
would like to start a draft proposal thread.

- Nick

···

On Sat, Mar 11, 2017 at 10:42 AM, Xiaodi Wu via swift-evolution < swift-evolution@swift.org> wrote:

On Fri, Mar 10, 2017 at 6:29 PM, James Froggatt <james.froggatt@me.com> > wrote:

On 11 Mar 2017, at 00:21, James Froggatt <james.froggatt@me.com> wrote:

On 11 Mar 2017, at 00:05, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

Some days ago, Ben Cohen laid out the criteria for helper functions in
the Standard Library. Here's some of his very enlightening text and the six
criteria:

The operation needs to carry its weight. Even once we have ABI stability,

so the size of the std lib becomes less of a concern as it could ship as
part of the OS, we still need to keep helper method growth under control.
APIs bristling with methods like an over-decorated Xmas tree are bad for
usability. As mentioned in the String manifesto, String+Foundation
currently has over 200 methods/properties. Helpers are no good if you can’t
find them to use them.

1. Is it truly a frequent operation?
2. Is the helper more readable? Is the composed equivalent obvious at a
glance?
3. Does the helper have the flexibility to cover all common cases?
4. Is there a correctness trap with the composed equivalent? Is there a
correctness trap with the helper?
5. Is there a performance trap with the composed equivalent? Or with the
helper?
6. Does the helper actually encourage misuse?

The reasons I'm opposed to adding `clamp` are as follows:

It is trivially composed from `min` and `max`, with no correctness traps.

As the discussion above shows, there are correctness traps when you have
a `clamp` operation that takes open ranges, whereas the composed form using
`min` and `max` does not suffer from the same issue.

It encourages misuse, because Dave's desired use case (for indices) works
*only* for arrays and falls down for collections. This is similar to the
problem which motivates removal of `enumerated()` as discussed in other
threads. In this case, it is not guaranteed that a collection with indices
`0..<10` has an index 9.

You make a good point, but then how exactly did the range-clamping
function make it into the standard library in the first place? I can't
think of frequent reason to want to clamp a range to within another range
putting my mind to it, yet a clamp function on the Bound type has uses with
arrays and offers a clear improvement to readability. Then there's the
(potential) correctness trap of mixing up min and max, which I find leads
me to need to double-check the logic after typing.

Seeing those criteria just makes it all the more frustrating that the
range-clamping version is the one to have made the cut.

Rereading, you're point is that the range-clamping version does solve a
correctness trap (and that the Bound version does not?). Could you explain
how you reached to this conclusion?

I'm not making that point. I'm just pointing out that these are the
current criteria for expanding the standard library.

If, looking back, you feel that the existing `clamped(to:)` doesn't fit
the criteria, then you can propose its removal. However, there are also
standards for changing existing APIs in Swift 4. The first one is that "the
existing syntax/API being changed must be actively harmful"--which is a bar
that `clamped(to:)` doesn't meet, IMO.

On Fri, Mar 10, 2017 at 4:48 PM, James Froggatt via swift-evolution < >> swift-evolution@swift.org> wrote:

This topic caught my attention. I support the idea, I'm currently using
an extension for this.

>>Should “16.clamped(to: 0..<10)” produce 9 or 10?

>9

Sounds good.

>>What about “16.clamped(to: 0..<0)”, which is an empty range?

>For `Int`? Crash (which, until about 5 minutes ago, is what I thought
would happen if you tried to create a range that’s empty like that). For
types that support it, I’d say NaN or something like “nil”/“empty” is the
most appropriate return value

Nasty but reasonable. I'd support it returning nil in this case, this
would provide a warning that the result may not be a valid number, as well
as providing this elegant range validation:

`if let index = candidate.clamped(to: array.indices) { … }`

(or a possible alternative spelling:)

`if let index = array.indices.clamp(candidate) { … }`

>>Does “16.0.clamped(to: 0..<10)” yield 10.0 or the next-smaller
representable Double?

>Next-smaller, IMHO. It’s not exactly semantically correct, but AFAIK
that’s as correct as Float/Double can be.

One could argue the most ‘correct’ value here is the closest
representation of the theoretical value, `10.0 - (1 / ∞)`, which should
clearly round to 10. However, this also rounds the result back out of the
range, meaning it's unsuitable as a result. Both possibilities are, for
lack of a better word, ugly.

>Mostly though I’d really like to be able to clamp to array indices,
which are pretty much always written as a `Range`, rather than a
`ClosedRange`. We could write the function for `Range` to only be generic
over `Comparable&Integer`, if the floating point corner cases are too much.

> - Dave Sweeris

My conclusion also. I'd like to see this added to the standard library,
if it's in scope for Swift 4.

Sidenote: I can't help but think index validation would be better solved
in many cases by an optional-returning array subscript (`array[ifPresent:
index]`), but I've seen this solution turned down several times due to the
lack of discoverability (read: lack of Xcode autocompletion, which I
originally thought was a bug until it stayed that way for ~3 years). I'd
also like to see this feature get added in some form, eventually.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution