[draft] Add `clamped(to:)` to the stdlib


(Nicholas Maccharoli) #1

Swift-Evolution,

After considering the indispensable feedback from the community here is a
slightly more polished draft.

If anyone is interested the draft can be downloaded here:
https://github.com/Nirma/swift-evolution/blob/clamp_
function/proposals/NNNN-add-clamp-function.md

Thank you so much,

- Nick

Add clamp(to:) to the stdlib

   - Proposal: SE-NNNN
   <https://github.com/Nirma/swift-evolution/blob/clamp_function/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/clamp_function/proposals/XXXX-filename.md>

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

This proposal aims to add functionality to the standard library for
clamping a value to a ClosedRange. 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/clamp_function/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 clampedfunction and was disappointed
it was not part of the standard library.

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.
<https://github.com/Nirma/swift-evolution/blob/clamp_function/proposals/NNNN-add-clamp-function.md#proposed-solution>Proposed
solution

The solution proposed is simply that there be a clamped(to:) function added
to the Swift Standard Library. The function would behave much like its name
describes.

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/clamp_function/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 {
        return clamped(to: range.lowerBound...(range.upperBound - 1))
    }
}

<https://github.com/Nirma/swift-evolution/blob/clamp_function/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/clamp_function/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/clamp_function/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/clamp_function/proposals/NNNN-add-clamp-function.md#alternatives-considered>Alternatives
considered

Aside from doing nothing, no other alternatives were considered.


(Nicholas Maccharoli) #2

I am starting to worry that I left something out.

···

On Tue, Mar 14, 2017 at 11:16 AM, Nicholas Maccharoli <nmaccharoli@gmail.com > wrote:

Swift-Evolution,

After considering the indispensable feedback from the community here is a
slightly more polished draft.

If anyone is interested the draft can be downloaded here:
https://github.com/Nirma/swift-evolution/blob/clamp_function
/proposals/NNNN-add-clamp-function.md

Thank you so much,

- Nick

Add clamp(to:) to the stdlib

   - Proposal: SE-NNNN
   <https://github.com/Nirma/swift-evolution/blob/clamp_function/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/clamp_function/proposals/XXXX-filename.md>

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

This proposal aims to add functionality to the standard library for
clamping a value to a ClosedRange. 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/clamp_function/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 clampedfunction and was
disappointed it was not part of the standard library.

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.

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

The solution proposed is simply that there be a clamped(to:) function
added to the Swift Standard Library. The function would behave much like
its name describes.

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/clamp_function/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 {
        return clamped(to: range.lowerBound...(range.upperBound - 1))
    }
}

<https://github.com/Nirma/swift-evolution/blob/clamp_function/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/clamp_function/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/clamp_function/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/clamp_function/proposals/NNNN-add-clamp-function.md#alternatives-considered>Alternatives
considered

Aside from doing nothing, no other alternatives were considered.


(Nate Cook) #3

Hi Nick,

I think this might have come up in an earlier thread, but clamped(to:) with a half-open range argument brings up the question of how to clamp to an empty range. As proposed, the implementation of this method would result in an error when passed an empty range:

    foo.clamped(to: 0..<0)
    // fatal error: Can't form Range with upperBound < lowerBound

Is that the desired behavior? Providing documentation for both new methods would help make it clear what the expected functionality is.

Thanks!
Nate

···

On Mar 13, 2017, at 9:16 PM, Nicholas Maccharoli via swift-evolution <swift-evolution@swift.org> wrote:

Swift-Evolution,

After considering the indispensable feedback from the community here is a slightly more polished draft.

If anyone is interested the draft can be downloaded here:
https://github.com/Nirma/swift-evolution/blob/clamp_function/proposals/NNNN-add-clamp-function.md

Thank you so much,

- Nick

Add clamp(to:) to the stdlib

Proposal: SE-NNNN <https://github.com/Nirma/swift-evolution/blob/clamp_function/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/clamp_function/proposals/XXXX-filename.md>
<https://github.com/Nirma/swift-evolution/blob/clamp_function/proposals/NNNN-add-clamp-function.md#introduction>Introduction

This proposal aims to add functionality to the standard library for clamping a value to a ClosedRange. 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/clamp_function/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 clampedfunction and was disappointed it was not part of the standard library.

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.

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

The solution proposed is simply that there be a clamped(to:) function added to the Swift Standard Library. The function would behave much like its name describes.

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) // 50
foo.clamped(to: 200...300) // 200
foo.clamped(to: 0...150) // 100

// Half-Open range variant

foo.clamped(to: 0..<50) // 49
foo.clamped(to: 200..<300) // 200
foo.clamped(to: 0..<150) // 100
<https://github.com/Nirma/swift-evolution/blob/clamp_function/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 {
        return clamped(to: range.lowerBound...(range.upperBound - 1))
    }
}
<https://github.com/Nirma/swift-evolution/blob/clamp_function/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/clamp_function/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/clamp_function/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/clamp_function/proposals/NNNN-add-clamp-function.md#alternatives-considered>Alternatives considered

Aside from doing nothing, no other alternatives were considered.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Neil Cowburn) #4

A suggestion, if I may: change the verb `clamped` to `bound`. The verb `clamp` means to immobilise or fix in place. That seems counterintuitive given the sole function argument is a range.

Compare the interpretation of clamped vs bound:

foo.clamped(to: 0...100) --> “The integer, foo, is clamped to a range between 0 and 100”

foo.bound(to: 0...100) --> “The integer, foo, is bound to a range between 0 and 100”

The latter, to me, reads more naturally than the former.

-- Neil

···

On 15/03/2017, 02:07, "Nicholas Maccharoli via swift-evolution" <swift-evolution-bounces@swift.org on behalf of swift-evolution@swift.org> wrote:

    I am starting to worry that I left something out.
    
    On Tue, Mar 14, 2017 at 11:16 AM, Nicholas Maccharoli <nmaccharoli@gmail.com> wrote:
    
    Swift-Evolution,
    
    After considering the indispensable feedback from the community here is a slightly more polished draft.
    
    If anyone is interested the draft can be downloaded here:
    https://github.com/Nirma/swift-evolution/blob/clamp_function/proposals/NNNN-add-clamp-function.md
    
    Thank you so much,
    
    - Nick
    
    Add clamp(to:) to the stdlib
    * Proposal: SE-NNNN <https://github.com/Nirma/swift-evolution/blob/clamp_function/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/clamp_function/proposals/XXXX-filename.md>
    
     <https://github.com/Nirma/swift-evolution/blob/clamp_function/proposals/NNNN-add-clamp-function.md#introduction>IntroductionThis proposal aims to add functionality to the standard library for clamping a value to a ClosedRange. 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/clamp_function/proposals/NNNN-add-clamp-function.md#motivation>MotivationThere have been quite a few times in my professional and personal programming life where I reached for a clampedfunction and was disappointed it was not part of the standard library.
    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.
     <https://github.com/Nirma/swift-evolution/blob/clamp_function/proposals/NNNN-add-clamp-function.md#proposed-solution>Proposed solutionThe solution proposed is simply that there be a clamped(to:) function added to the Swift Standard Library. The function would behave much like its name describes.
    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/clamp_function/proposals/NNNN-add-clamp-function.md#detailed-design>Detailed designThe 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 {
            return clamped(to: range.lowerBound…(range.upperBound - 1))
        }
    }
     <https://github.com/Nirma/swift-evolution/blob/clamp_function/proposals/NNNN-add-clamp-function.md#source-compatibility>Source compatibilityThis feature is purely additive; it has no effect on source compatibility.
     <https://github.com/Nirma/swift-evolution/blob/clamp_function/proposals/NNNN-add-clamp-function.md#effect-on-abi-stability>Effect on ABI stabilityThis feature is purely additive; it has no effect on ABI stability.
     <https://github.com/Nirma/swift-evolution/blob/clamp_function/proposals/NNNN-add-clamp-function.md#effect-on-api-resilience>Effect on API resilienceThe proposed function would become part of the API but purely additive.
     <https://github.com/Nirma/swift-evolution/blob/clamp_function/proposals/NNNN-add-clamp-function.md#alternatives-considered>Alternatives consideredAside from doing nothing, no other alternatives were considered.
    
    _______________________________________________
    swift-evolution mailing list
    swift-evolution@swift.org
    https://lists.swift.org/mailman/listinfo/swift-evolution


(David Rönnqvist) #5

It might be worth mentioning (for background) that the various Range types already have a `clamped(to:)`. The proposed solution is consistent with that (as opposed to for example a free function).
I’m not sure if a free function should be considered an alternative. It’s probably fine not listing any alternatives.
The motivation section could benefit from one or two examples of what/when clamping is used. That could help someone who haven’t used it to judge it’s usefulness as part of the standard library.

- David

···

On 15 Mar 2017, at 03:07, Nicholas Maccharoli via swift-evolution <swift-evolution@swift.org> wrote:

I am starting to worry that I left something out.

On Tue, Mar 14, 2017 at 11:16 AM, Nicholas Maccharoli <nmaccharoli@gmail.com <mailto:nmaccharoli@gmail.com>> wrote:
Swift-Evolution,

After considering the indispensable feedback from the community here is a slightly more polished draft.

If anyone is interested the draft can be downloaded here:
https://github.com/Nirma/swift-evolution/blob/clamp_function/proposals/NNNN-add-clamp-function.md

Thank you so much,

- Nick

Add clamp(to:) to the stdlib

Proposal: SE-NNNN <https://github.com/Nirma/swift-evolution/blob/clamp_function/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/clamp_function/proposals/XXXX-filename.md>
<https://github.com/Nirma/swift-evolution/blob/clamp_function/proposals/NNNN-add-clamp-function.md#introduction>Introduction

This proposal aims to add functionality to the standard library for clamping a value to a ClosedRange. 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/clamp_function/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 clampedfunction and was disappointed it was not part of the standard library.

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.

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

The solution proposed is simply that there be a clamped(to:) function added to the Swift Standard Library. The function would behave much like its name describes.

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) // 50
foo.clamped(to: 200...300) // 200
foo.clamped(to: 0...150) // 100

// Half-Open range variant

foo.clamped(to: 0..<50) // 49
foo.clamped(to: 200..<300) // 200
foo.clamped(to: 0..<150) // 100
<https://github.com/Nirma/swift-evolution/blob/clamp_function/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 {
        return clamped(to: range.lowerBound...(range.upperBound - 1))
    }
}
<https://github.com/Nirma/swift-evolution/blob/clamp_function/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/clamp_function/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/clamp_function/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/clamp_function/proposals/NNNN-add-clamp-function.md#alternatives-considered>Alternatives considered

Aside from doing nothing, no other alternatives were considered.

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


(Sean Heber) #6

Clamping in this context is a term of art.

l8r
Sean

···

On Mar 15, 2017, at 5:22 AM, Neil Cowburn via swift-evolution <swift-evolution@swift.org> wrote:

A suggestion, if I may: change the verb `clamped` to `bound`. The verb `clamp` means to immobilise or fix in place. That seems counterintuitive given the sole function argument is a range.

Compare the interpretation of clamped vs bound:

foo.clamped(to: 0...100) --> “The integer, foo, is clamped to a range between 0 and 100”

foo.bound(to: 0...100) --> “The integer, foo, is bound to a range between 0 and 100”

The latter, to me, reads more naturally than the former.

-- Neil

On 15/03/2017, 02:07, "Nicholas Maccharoli via swift-evolution" <swift-evolution-bounces@swift.org on behalf of swift-evolution@swift.org> wrote:

   I am starting to worry that I left something out.

   On Tue, Mar 14, 2017 at 11:16 AM, Nicholas Maccharoli <nmaccharoli@gmail.com> wrote:

   Swift-Evolution,

   After considering the indispensable feedback from the community here is a slightly more polished draft.

   If anyone is interested the draft can be downloaded here:
   https://github.com/Nirma/swift-evolution/blob/clamp_function/proposals/NNNN-add-clamp-function.md

   Thank you so much,

   - Nick

   Add clamp(to:) to the stdlib
   * Proposal: SE-NNNN <https://github.com/Nirma/swift-evolution/blob/clamp_function/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/clamp_function/proposals/XXXX-filename.md>

    <https://github.com/Nirma/swift-evolution/blob/clamp_function/proposals/NNNN-add-clamp-function.md#introduction>IntroductionThis proposal aims to add functionality to the standard library for clamping a value to a ClosedRange. 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/clamp_function/proposals/NNNN-add-clamp-function.md#motivation>MotivationThere have been quite a few times in my professional and personal programming life where I reached for a clampedfunction and was disappointed it was not part of the standard library.
   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.
    <https://github.com/Nirma/swift-evolution/blob/clamp_function/proposals/NNNN-add-clamp-function.md#proposed-solution>Proposed solutionThe solution proposed is simply that there be a clamped(to:) function added to the Swift Standard Library. The function would behave much like its name describes.
   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/clamp_function/proposals/NNNN-add-clamp-function.md#detailed-design>Detailed designThe 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 {
           return clamped(to: range.lowerBound...(range.upperBound - 1))
       }
   }
    <https://github.com/Nirma/swift-evolution/blob/clamp_function/proposals/NNNN-add-clamp-function.md#source-compatibility>Source compatibilityThis feature is purely additive; it has no effect on source compatibility.
    <https://github.com/Nirma/swift-evolution/blob/clamp_function/proposals/NNNN-add-clamp-function.md#effect-on-abi-stability>Effect on ABI stabilityThis feature is purely additive; it has no effect on ABI stability.
    <https://github.com/Nirma/swift-evolution/blob/clamp_function/proposals/NNNN-add-clamp-function.md#effect-on-api-resilience>Effect on API resilienceThe proposed function would become part of the API but purely additive.
    <https://github.com/Nirma/swift-evolution/blob/clamp_function/proposals/NNNN-add-clamp-function.md#alternatives-considered>Alternatives consideredAside from doing nothing, no other alternatives were considered.

   _______________________________________________
   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


(Jaden Geller) #7

Hi Nick,

I think this might have come up in an earlier thread, but clamped(to:) with a half-open range argument brings up the question of how to clamp to an empty range. As proposed, the implementation of this method would result in an error when passed an empty range:

    foo.clamped(to: 0..<0)
    // fatal error: Can't form Range with upperBound < lowerBound

Ahh, good catch! The implementation ought to add something like:

guard !range.isEmpty else { fatalError("Cannot clamp to an empty range") }

I don’t think that this is problematic. Ideally we could express in the type-system that the range must not be empty, but it is definitely not worthwhile creating a `NonemptyRange` type.

···

On Mar 15, 2017, at 12:40 PM, Nate Cook via swift-evolution <swift-evolution@swift.org> wrote:

Is that the desired behavior? Providing documentation for both new methods would help make it clear what the expected functionality is.

Thanks!
Nate

On Mar 13, 2017, at 9:16 PM, Nicholas Maccharoli via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Swift-Evolution,

After considering the indispensable feedback from the community here is a slightly more polished draft.

If anyone is interested the draft can be downloaded here:
https://github.com/Nirma/swift-evolution/blob/clamp_function/proposals/NNNN-add-clamp-function.md

Thank you so much,

- Nick

Add clamp(to:) to the stdlib

Proposal: SE-NNNN <https://github.com/Nirma/swift-evolution/blob/clamp_function/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/clamp_function/proposals/XXXX-filename.md>
<https://github.com/Nirma/swift-evolution/blob/clamp_function/proposals/NNNN-add-clamp-function.md#introduction>Introduction

This proposal aims to add functionality to the standard library for clamping a value to a ClosedRange. 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/clamp_function/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 clampedfunction and was disappointed it was not part of the standard library.

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.

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

The solution proposed is simply that there be a clamped(to:) function added to the Swift Standard Library. The function would behave much like its name describes.

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) // 50
foo.clamped(to: 200...300) // 200
foo.clamped(to: 0...150) // 100

// Half-Open range variant

foo.clamped(to: 0..<50) // 49
foo.clamped(to: 200..<300) // 200
foo.clamped(to: 0..<150) // 100
<https://github.com/Nirma/swift-evolution/blob/clamp_function/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 {
        return clamped(to: range.lowerBound...(range.upperBound - 1))
    }
}
<https://github.com/Nirma/swift-evolution/blob/clamp_function/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/clamp_function/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/clamp_function/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/clamp_function/proposals/NNNN-add-clamp-function.md#alternatives-considered>Alternatives considered

Aside from doing nothing, no other alternatives were considered.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto: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


(Haravikk) #8

I'd say that's the correct behaviour; it makes no sense to call a clamp method without anything to actually clamp to; an empty range contains nothing, so not only can't include the value you're testing against, but also has no bounds to clamp to instead.

···

On 15 Mar 2017, at 19:40, Nate Cook via swift-evolution <swift-evolution@swift.org> wrote:

Hi Nick,

I think this might have come up in an earlier thread, but clamped(to:) with a half-open range argument brings up the question of how to clamp to an empty range. As proposed, the implementation of this method would result in an error when passed an empty range:

    foo.clamped(to: 0..<0)
    // fatal error: Can't form Range with upperBound < lowerBound

Is that the desired behavior? Providing documentation for both new methods would help make it clear what the expected functionality is.


(Nicholas Maccharoli) #9

Right, there were a few things missing!
Thanks so much for pointing them out everyone.

Dave - Great idea! I have updated the motivation section section as you
suggested!

Neil - Yes I also think the wording could be a bit better but since the
word `clamped` is already being used
          I thought I would keep it consistent.

Sean - Looks as if its a term of art to me as well.

Nate,

Good catch! Yes I also thing clamping on an empty range should be a fatal
error as well.
An empty range is impossible to create with `ClosedRange` so I left the
implementation
of that alone, but it is possible with `Range` so I updated the extension
on `Strideable` like so:

extension Strideable where Stride: Integer {
    func clamped(to range: Range<Self>) -> Self {
        if range.isEmpty { fatalError("Can't form Range with
upperBound < lowerBound") }
        return clamped(to: range.lowerBound...(range.upperBound - 1))
    }
}

Jaden,

Yeah I think a simple `if` check would work as well.

···

On Thu, Mar 16, 2017 at 10:19 AM, Jaden Geller <jaden.geller@gmail.com> wrote:

On Mar 15, 2017, at 12:40 PM, Nate Cook via swift-evolution < > swift-evolution@swift.org> wrote:

Hi Nick,

I think this might have come up in an earlier thread, but clamped(to:)
with a half-open range argument brings up the question of how to clamp to
an empty range. As proposed, the implementation of this method would result
in an error when passed an empty range:

    foo.clamped(to: 0..<0)
    // fatal error: Can't form Range with upperBound < lowerBound

Ahh, good catch! The implementation ought to add something like:

guard !range.isEmpty else { fatalError("Cannot clamp to an empty range") }

I don’t think that this is problematic. Ideally we could express in the
type-system that the range must not be empty, but it is definitely not
worthwhile creating a `NonemptyRange` type.

Is that the desired behavior? Providing documentation for both new methods
would help make it clear what the expected functionality is.

Thanks!
Nate

On Mar 13, 2017, at 9:16 PM, Nicholas Maccharoli via swift-evolution < > swift-evolution@swift.org> wrote:

Swift-Evolution,

After considering the indispensable feedback from the community here is a
slightly more polished draft.

If anyone is interested the draft can be downloaded here:
https://github.com/Nirma/swift-evolution/blob/clamp_function
/proposals/NNNN-add-clamp-function.md

Thank you so much,

- Nick

Add clamp(to:) to the stdlib

   - Proposal: SE-NNNN
   <https://github.com/Nirma/swift-evolution/blob/clamp_function/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/clamp_function/proposals/XXXX-filename.md>

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

This proposal aims to add functionality to the standard library for
clamping a value to a ClosedRange. 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/clamp_function/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 clampedfunction and was
disappointed it was not part of the standard library.

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.

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

The solution proposed is simply that there be a clamped(to:) function
added to the Swift Standard Library. The function would behave much like
its name describes.

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/clamp_function/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 {
        return clamped(to: range.lowerBound...(range.upperBound - 1))
    }
}

<https://github.com/Nirma/swift-evolution/blob/clamp_function/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/clamp_function/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/clamp_function/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/clamp_function/proposals/NNNN-add-clamp-function.md#alternatives-considered>Alternatives
considered
Aside from doing nothing, no other alternatives were considered.
_______________________________________________
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


(Jaden Geller) #10

I would suggest using guard. It is more idiomatic Swift for something that “fails out”.

Also, I think this is a bad error message. The `Range` was already created! There was no problem forming it. It was passed as the argument, no problem at all. The problem is trying to *clamp* to an empty range, not forming an empty range. I would rephrase it to say something like "Cannot clamp to an empty range”. No reason to redefine what an empty range is by mentioning `upperBound < lowerBound`.

Cheers,
Jaden Geller

···

On Mar 15, 2017, at 7:33 PM, Nicholas Maccharoli <nmaccharoli@gmail.com> wrote:

Right, there were a few things missing!
Thanks so much for pointing them out everyone.

Dave - Great idea! I have updated the motivation section section as you suggested!

Neil - Yes I also think the wording could be a bit better but since the word `clamped` is already being used
          I thought I would keep it consistent.

Sean - Looks as if its a term of art to me as well.

Nate,

Good catch! Yes I also thing clamping on an empty range should be a fatal error as well.
An empty range is impossible to create with `ClosedRange` so I left the implementation
of that alone, but it is possible with `Range` so I updated the extension on `Strideable` like so:

extension Strideable where Stride: Integer {
    func clamped(to range: Range<Self>) -> Self {
        if range.isEmpty { fatalError("Can't form Range with upperBound < lowerBound") }
        return clamped(to: range.lowerBound...(range.upperBound - 1))
    }
}

Jaden,

Yeah I think a simple `if` check would work as well.


(Nicholas Maccharoli) #11

Here is the updated version of the proposal:

https://github.com/Nirma/swift-evolution/blob/clamp_function/proposals/NNNN-add-clamp-function.md

Add clamp(to:) to the stdlib

   - Proposal: SE-NNNN
   <https://github.com/Nirma/swift-evolution/blob/clamp_function/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/clamp_function/proposals/XXXX-filename.md>

<https://github.com/Nirma/swift-evolution/blob/clamp_function/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/clamp_function/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/clamp_function/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/clamp_function/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 {
        if range.isEmpty { fatalError("Can't form Range with
upperBound < lowerBound") }
        return clamped(to: range.lowerBound...(range.upperBound - 1))
    }
}

<https://github.com/Nirma/swift-evolution/blob/clamp_function/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/clamp_function/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/clamp_function/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/clamp_function/proposals/NNNN-add-clamp-function.md#alternatives-considered>Alternatives
considered

Aside from doing nothing, no other alternatives were considered.

···

On Thu, Mar 16, 2017 at 11:33 AM, Nicholas Maccharoli <nmaccharoli@gmail.com > wrote:

Right, there were a few things missing!
Thanks so much for pointing them out everyone.

Dave - Great idea! I have updated the motivation section section as you
suggested!

Neil - Yes I also think the wording could be a bit better but since the
word `clamped` is already being used
          I thought I would keep it consistent.

Sean - Looks as if its a term of art to me as well.

Nate,

Good catch! Yes I also thing clamping on an empty range should be a fatal
error as well.
An empty range is impossible to create with `ClosedRange` so I left the
implementation
of that alone, but it is possible with `Range` so I updated the extension
on `Strideable` like so:

extension Strideable where Stride: Integer {
    func clamped(to range: Range<Self>) -> Self {
        if range.isEmpty { fatalError("Can't form Range with upperBound < lowerBound") }
        return clamped(to: range.lowerBound...(range.upperBound - 1))
    }
}

Jaden,

Yeah I think a simple `if` check would work as well.

On Thu, Mar 16, 2017 at 10:19 AM, Jaden Geller <jaden.geller@gmail.com> > wrote:

On Mar 15, 2017, at 12:40 PM, Nate Cook via swift-evolution < >> swift-evolution@swift.org> wrote:

Hi Nick,

I think this might have come up in an earlier thread, but clamped(to:)
with a half-open range argument brings up the question of how to clamp to
an empty range. As proposed, the implementation of this method would result
in an error when passed an empty range:

    foo.clamped(to: 0..<0)
    // fatal error: Can't form Range with upperBound < lowerBound

Ahh, good catch! The implementation ought to add something like:

guard !range.isEmpty else { fatalError("Cannot clamp to an empty range")
}

I don’t think that this is problematic. Ideally we could express in the
type-system that the range must not be empty, but it is definitely not
worthwhile creating a `NonemptyRange` type.

Is that the desired behavior? Providing documentation for both new
methods would help make it clear what the expected functionality is.

Thanks!
Nate

On Mar 13, 2017, at 9:16 PM, Nicholas Maccharoli via swift-evolution < >> swift-evolution@swift.org> wrote:

Swift-Evolution,

After considering the indispensable feedback from the community here is a
slightly more polished draft.

If anyone is interested the draft can be downloaded here:
https://github.com/Nirma/swift-evolution/blob/clamp_function
/proposals/NNNN-add-clamp-function.md

Thank you so much,

- Nick

Add clamp(to:) to the stdlib

   - Proposal: SE-NNNN
   <https://github.com/Nirma/swift-evolution/blob/clamp_function/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/clamp_function/proposals/XXXX-filename.md>

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

This proposal aims to add functionality to the standard library for
clamping a value to a ClosedRange. 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/clamp_function/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 clampedfunction and was
disappointed it was not part of the standard library.

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.

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

The solution proposed is simply that there be a clamped(to:) function
added to the Swift Standard Library. The function would behave much like
its name describes.

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/clamp_function/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 {
        return clamped(to: range.lowerBound...(range.upperBound - 1))
    }
}

<https://github.com/Nirma/swift-evolution/blob/clamp_function/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/clamp_function/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/clamp_function/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/clamp_function/proposals/NNNN-add-clamp-function.md#alternatives-considered>Alternatives
considered
Aside from doing nothing, no other alternatives were considered.
_______________________________________________
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


(Nicholas Maccharoli) #12

Jaden,

Yes that error message is not so great.
As for the use of guard `if someCondition { fatalError(...)}` seems to be
a common way of phrasing fatal errors
in the standard library but guard works just as well.

I updated the proposal to have the following definition:

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))
    }
}

- Nick

···

On Thu, Mar 16, 2017 at 11:57 AM, Jaden Geller <jaden.geller@gmail.com> wrote:

On Mar 15, 2017, at 7:33 PM, Nicholas Maccharoli <nmaccharoli@gmail.com> > wrote:

Right, there were a few things missing!
Thanks so much for pointing them out everyone.

Dave - Great idea! I have updated the motivation section section as you
suggested!

Neil - Yes I also think the wording could be a bit better but since the
word `clamped` is already being used
          I thought I would keep it consistent.

Sean - Looks as if its a term of art to me as well.

Nate,

Good catch! Yes I also thing clamping on an empty range should be a fatal
error as well.
An empty range is impossible to create with `ClosedRange` so I left the
implementation
of that alone, but it is possible with `Range` so I updated the extension
on `Strideable` like so:

extension Strideable where Stride: Integer {
    func clamped(to range: Range<Self>) -> Self {
        if range.isEmpty { fatalError("Can't form Range with upperBound < lowerBound") }
        return clamped(to: range.lowerBound...(range.upperBound - 1))
    }
}

Jaden,

Yeah I think a simple `if` check would work as well.

I would suggest using guard. It is more idiomatic Swift for something that
“fails out”.

Also, I think this is a bad error message. The `Range` was already
created! There was no problem forming it. It was passed as the argument, no
problem at all. The problem is trying to *clamp* to an empty range, not
forming an empty range. I would rephrase it to say something like "Cannot
clamp to an empty range”. No reason to redefine what an empty range is by
mentioning `upperBound < lowerBound`.

Cheers,
Jaden Geller


(Jaden Geller) #13

Jaden,

Yes that error message is not so great.
As for the use of guard `if someCondition { fatalError(...)}` seems to be a common way of phrasing fatal errors
in the standard library but guard works just as well.

I’d guess that’s because most of the standard library predates the existence of `guard`, but I’m not sure! Either spelling is probably fine (and more the topic of a pull request than a SE review :slightly_smiling_face:).

I updated the proposal to have the following definition:

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))
    }
}

- Nick

Right, there were a few things missing!
Thanks so much for pointing them out everyone.

Dave - Great idea! I have updated the motivation section section as you suggested!

Neil - Yes I also think the wording could be a bit better but since the word `clamped` is already being used
          I thought I would keep it consistent.

Sean - Looks as if its a term of art to me as well.

Nate,

Good catch! Yes I also thing clamping on an empty range should be a fatal error as well.
An empty range is impossible to create with `ClosedRange` so I left the implementation
of that alone, but it is possible with `Range` so I updated the extension on `Strideable` like so:

extension Strideable where Stride: Integer {
    func clamped(to range: Range<Self>) -> Self {
        if range.isEmpty { fatalError("Can't form Range with upperBound < lowerBound") }
        return clamped(to: range.lowerBound...(range.upperBound - 1))
    }
}

Jaden,

Yeah I think a simple `if` check would work as well.

I would suggest using guard. It is more idiomatic Swift for something that “fails out”.

Also, I think this is a bad error message. The `Range` was already created! There was no problem forming it. It was passed as the argument, no problem at all. The problem is trying to *clamp* to an empty range, not forming an empty range. I would rephrase it to say something like "Cannot clamp to an empty range”. No reason to redefine what an empty range is by mentioning `upperBound < lowerBound`.

Cheers,
Jaden Geller

Cheers,
Jaden Geller

···

On Mar 15, 2017, at 8:21 PM, Nicholas Maccharoli <nmaccharoli@gmail.com> wrote:
On Thu, Mar 16, 2017 at 11:57 AM, Jaden Geller <jaden.geller@gmail.com <mailto:jaden.geller@gmail.com>> wrote:

On Mar 15, 2017, at 7:33 PM, Nicholas Maccharoli <nmaccharoli@gmail.com <mailto:nmaccharoli@gmail.com>> wrote: