[Proposal] Add clamp(to:) to the stdlib


(Nicholas Maccharoli) #1

​Swift-Evolution,

I propose that `clamp(to:)` be added to the standard library as detailed
in the proposal written below.
There is also an open pull request to the swift-evolution repository open
here:

https://github.com/apple/swift-evolution/pull/641

The idea was discussed on previous threads and a few people helped make
the draft proposal better.
Since the reception of the ​idea was overall good, I thought it might be
worth it
to formally propose the idea.

Please tell me what you think.

- Nick

Add clamp(to:) to the stdlib

   - Proposal: SE-NNNN
   <https://github.com/Nirma/swift-evolution/blob/2ecfd45f4636e44f1000177fa509b1412cebef92/proposals/NNNN-filename.md>
   - Authors: Nicholas Maccharoli <https://github.com/Nirma>
   - Review Manager: TBD
   - Status: Awaiting review

*During the review process, add the following fields as needed:*

   - Decision Notes: Rationale
   <https://lists.swift.org/pipermail/swift-evolution/>, Additional
   Commentary <https://lists.swift.org/pipermail/swift-evolution/>
   - Bugs: SR-NNNN <https://bugs.swift.org/browse/SR-NNNN>, SR-MMMM
   <https://bugs.swift.org/browse/SR-MMMM>
   - Previous Revision: 1
   <https://github.com/apple/swift-evolution/blob/...commit-ID.../proposals/NNNN-filename.md>
   - Previous Proposal: SE-XXXX
   <https://github.com/Nirma/swift-evolution/blob/2ecfd45f4636e44f1000177fa509b1412cebef92/proposals/XXXX-filename.md>

<https://github.com/Nirma/swift-evolution/blob/2ecfd45f4636e44f1000177fa509b1412cebef92/proposals/NNNN-add-clamp-function.md#introduction>
Introduction

This proposal aims to add functionality to the standard library for
clamping a value to a provided Range. The proposed function would allow the
user to specify a range to clamp a value to where if the value fell within
the range, the value would be returned as is, if the value being clamped
exceeded the upper or lower bound in value the value of the boundary the
value exceeded would be returned.

Swift-evolution thread: Add a clamp function to Algorithm.swift
<https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170306/thread.html#33674>
<https://github.com/Nirma/swift-evolution/blob/2ecfd45f4636e44f1000177fa509b1412cebef92/proposals/NNNN-add-clamp-function.md#motivation>
Motivation

There have been quite a few times in my professional and personal
programming life where I reached for a function to limit a value to a given
range and was disappointed it was not part of the standard library.

There already exists an extension to CountableRange in the standard library
implementing clamped(to:) that will limit the calling range to that of the
provided range, so having the same functionality but just for types that
conform to the Comparable protocol would be conceptually consistent.

Having functionality like clamped(to:) added to Comparable as a protocol
extension would benefit users of the Swift language who wish to guarantee
that a value is kept within bounds, perhaps one example of this coming in
handy would be to limit the result of some calculation between two
acceptable numerical limits, say the bounds of a coordinate system.
<https://github.com/Nirma/swift-evolution/blob/2ecfd45f4636e44f1000177fa509b1412cebef92/proposals/NNNN-add-clamp-function.md#proposed-solution>Proposed
solution

The proposed solution is to add a clamped(to:) function to the Swift
Standard Library as an extension to Comparable and Strideable. The function
would return a value within the bounds of the provided range, if the value
clamped(to:) is being called on falls within the provided range then the
original value would be returned. If the value was less or greater than the
bounds of the provided range then the respective lower or upper bound of
the range would be returned.

It does not make sense to call clamped(to:) with an empty range, therefore
calling clamped(to:) and passing an empty range like foo.clamped(to:
0..<0) would
result in a fatal error.

Given a clamped(to:) function existed it could be called in the following
way, yielding the results in the adjacent comments:

let foo = 100
// Closed range variant
foo.clamped(to: 0...50) // 50foo.clamped(to: 200...300) //
200foo.clamped(to: 0...150) // 100// Half-Open range variant
foo.clamped(to: 0..<50) // 49foo.clamped(to: 200..<300) //
200foo.clamped(to: 0..<150) // 100

<https://github.com/Nirma/swift-evolution/blob/2ecfd45f4636e44f1000177fa509b1412cebef92/proposals/NNNN-add-clamp-function.md#detailed-design>Detailed
design

The implementation of clamped(to:) that is being proposed is composed of
two protocol extensions; one protocol extension on Comparable and another
on Strideable.

The implementation for clamped(to:) as an extension to Comparable accepting
a range of type ClosedRange<Self>would look like the following:

extension Comparable {
    func clamped(to range: ClosedRange<Self>) -> Self {
        if self > range.upperBound {
            return range.upperBound
        } else if self < range.lowerBound {
            return range.lowerBound
        } else {
            return self
        }
    }
}

The implementation of clamped(to:) as an extension on Strideable would be
confined to cases where the stride is of type Integer. The implementation
would be as follows:

extension Strideable where Stride: Integer {
    func clamped(to range: Range<Self>) -> Self {
        guard !range.isEmpty { fatalError("Can not clamp to an empty range.") }
        return clamped(to: range.lowerBound...(range.upperBound - 1))
    }
}

<https://github.com/Nirma/swift-evolution/blob/2ecfd45f4636e44f1000177fa509b1412cebef92/proposals/NNNN-add-clamp-function.md#source-compatibility>Source
compatibility

This feature is purely additive; it has no effect on source compatibility.
<https://github.com/Nirma/swift-evolution/blob/2ecfd45f4636e44f1000177fa509b1412cebef92/proposals/NNNN-add-clamp-function.md#effect-on-abi-stability>Effect
on ABI stability

This feature is purely additive; it has no effect on ABI stability.
<https://github.com/Nirma/swift-evolution/blob/2ecfd45f4636e44f1000177fa509b1412cebef92/proposals/NNNN-add-clamp-function.md#effect-on-api-resilience>Effect
on API resilience

The proposed function would become part of the API but purely additive.
<https://github.com/Nirma/swift-evolution/blob/2ecfd45f4636e44f1000177fa509b1412cebef92/proposals/NNNN-add-clamp-function.md#alternatives-considered>Alternatives
considered

Aside from doing nothing, no other alternatives were considered.


(Nicholas Maccharoli) #2

Sorry did I skip a step in the proposal process?

···

On Tue, Mar 21, 2017 at 7:09 PM, Nicholas Maccharoli <nmaccharoli@gmail.com> wrote:

​Swift-Evolution,

I propose that `clamp(to:)` be added to the standard library as detailed
in the proposal written below.
There is also an open pull request to the swift-evolution repository open
here:

https://github.com/apple/swift-evolution/pull/641

The idea was discussed on previous threads and a few people helped make
the draft proposal better.
Since the reception of the ​idea was overall good, I thought it might be
worth it
to formally propose the idea.

Please tell me what you think.

- Nick

Add clamp(to:) to the stdlib

   - Proposal: SE-NNNN
   <https://github.com/Nirma/swift-evolution/blob/2ecfd45f4636e44f1000177fa509b1412cebef92/proposals/NNNN-filename.md>
   - Authors: Nicholas Maccharoli <https://github.com/Nirma>
   - Review Manager: TBD
   - Status: Awaiting review

*During the review process, add the following fields as needed:*

   - Decision Notes: Rationale
   <https://lists.swift.org/pipermail/swift-evolution/>, Additional
   Commentary <https://lists.swift.org/pipermail/swift-evolution/>
   - Bugs: SR-NNNN <https://bugs.swift.org/browse/SR-NNNN>, SR-MMMM
   <https://bugs.swift.org/browse/SR-MMMM>
   - Previous Revision: 1
   <https://github.com/apple/swift-evolution/blob/...commit-ID.../proposals/NNNN-filename.md>
   - Previous Proposal: SE-XXXX
   <https://github.com/Nirma/swift-evolution/blob/2ecfd45f4636e44f1000177fa509b1412cebef92/proposals/XXXX-filename.md>

<https://github.com/Nirma/swift-evolution/blob/2ecfd45f4636e44f1000177fa509b1412cebef92/proposals/NNNN-add-clamp-function.md#introduction>
Introduction

This proposal aims to add functionality to the standard library for
clamping a value to a provided Range. The proposed function would allow
the user to specify a range to clamp a value to where if the value fell
within the range, the value would be returned as is, if the value being
clamped exceeded the upper or lower bound in value the value of the
boundary the value exceeded would be returned.

Swift-evolution thread: Add a clamp function to Algorithm.swift
<https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170306/thread.html#33674>

<https://github.com/Nirma/swift-evolution/blob/2ecfd45f4636e44f1000177fa509b1412cebef92/proposals/NNNN-add-clamp-function.md#motivation>
Motivation

There have been quite a few times in my professional and personal
programming life where I reached for a function to limit a value to a given
range and was disappointed it was not part of the standard library.

There already exists an extension to CountableRange in the standard
library implementing clamped(to:) that will limit the calling range to
that of the provided range, so having the same functionality but just for
types that conform to the Comparable protocol would be conceptually
consistent.

Having functionality like clamped(to:) added to Comparable as a protocol
extension would benefit users of the Swift language who wish to guarantee
that a value is kept within bounds, perhaps one example of this coming in
handy would be to limit the result of some calculation between two
acceptable numerical limits, say the bounds of a coordinate system.

<https://github.com/Nirma/swift-evolution/blob/2ecfd45f4636e44f1000177fa509b1412cebef92/proposals/NNNN-add-clamp-function.md#proposed-solution>Proposed
solution

The proposed solution is to add a clamped(to:) function to the Swift
Standard Library as an extension to Comparable and Strideable. The
function would return a value within the bounds of the provided range, if
the value clamped(to:) is being called on falls within the provided range
then the original value would be returned. If the value was less or greater
than the bounds of the provided range then the respective lower or upper
bound of the range would be returned.

It does not make sense to call clamped(to:) with an empty range,
therefore calling clamped(to:) and passing an empty range like foo.clamped(to:
0..<0) would result in a fatal error.

Given a clamped(to:) function existed it could be called in the following
way, yielding the results in the adjacent comments:

let foo = 100
// Closed range variant
foo.clamped(to: 0...50) // 50foo.clamped(to: 200...300) // 200foo.clamped(to: 0...150) // 100// Half-Open range variant
foo.clamped(to: 0..<50) // 49foo.clamped(to: 200..<300) // 200foo.clamped(to: 0..<150) // 100

<https://github.com/Nirma/swift-evolution/blob/2ecfd45f4636e44f1000177fa509b1412cebef92/proposals/NNNN-add-clamp-function.md#detailed-design>Detailed
design

The implementation of clamped(to:) that is being proposed is composed of
two protocol extensions; one protocol extension on Comparable and another
on Strideable.

The implementation for clamped(to:) as an extension to Comparable accepting
a range of type ClosedRange<Self>would look like the following:

extension Comparable {
    func clamped(to range: ClosedRange<Self>) -> Self {
        if self > range.upperBound {
            return range.upperBound
        } else if self < range.lowerBound {
            return range.lowerBound
        } else {
            return self
        }
    }
}

The implementation of clamped(to:) as an extension on Strideable would be
confined to cases where the stride is of type Integer. The implementation
would be as follows:

extension Strideable where Stride: Integer {
    func clamped(to range: Range<Self>) -> Self {
        guard !range.isEmpty { fatalError("Can not clamp to an empty range.") }
        return clamped(to: range.lowerBound...(range.upperBound - 1))
    }
}

<https://github.com/Nirma/swift-evolution/blob/2ecfd45f4636e44f1000177fa509b1412cebef92/proposals/NNNN-add-clamp-function.md#source-compatibility>Source
compatibility

This feature is purely additive; it has no effect on source compatibility.

<https://github.com/Nirma/swift-evolution/blob/2ecfd45f4636e44f1000177fa509b1412cebef92/proposals/NNNN-add-clamp-function.md#effect-on-abi-stability>Effect
on ABI stability

This feature is purely additive; it has no effect on ABI stability.

<https://github.com/Nirma/swift-evolution/blob/2ecfd45f4636e44f1000177fa509b1412cebef92/proposals/NNNN-add-clamp-function.md#effect-on-api-resilience>Effect
on API resilience

The proposed function would become part of the API but purely additive.

<https://github.com/Nirma/swift-evolution/blob/2ecfd45f4636e44f1000177fa509b1412cebef92/proposals/NNNN-add-clamp-function.md#alternatives-considered>Alternatives
considered

Aside from doing nothing, no other alternatives were considered.