Skip to content

Commit 74c0420

Browse files
Nicholas MaccharoliDave Abrahams
authored andcommitted
[Proposal] Add clamp(to:) to the stdlib (#641)
* First draft of proposal for adding `clamp(to:)` * Update proposal to include support for Half-Open Ranges * make example code use let * Update proposal to handle empty ranges * Provide better error message for clamping with empty range. * Rewrite logic of `clamp` function to not treat empty ranges as a special case. * Remove outdated description of empty range clause
1 parent b922ef9 commit 74c0420

File tree

1 file changed

+109
-0
lines changed

1 file changed

+109
-0
lines changed

proposals/NNNN-add-clamp-function.md

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# Add clamp(to:) to the stdlib
2+
3+
* Proposal: [SE-NNNN](NNNN-filename.md)
4+
* Authors: [Nicholas Maccharoli](https://github.com/Nirma)
5+
* Review Manager: TBD
6+
* Status: **Awaiting review**
7+
8+
*During the review process, add the following fields as needed:*
9+
10+
* Decision Notes: [Rationale](https://lists.swift.org/pipermail/swift-evolution/), [Additional Commentary](https://lists.swift.org/pipermail/swift-evolution/)
11+
* Bugs: [SR-NNNN](https://bugs.swift.org/browse/SR-NNNN), [SR-MMMM](https://bugs.swift.org/browse/SR-MMMM)
12+
* Previous Revision: [1](https://github.com/apple/swift-evolution/blob/...commit-ID.../proposals/NNNN-filename.md)
13+
* Previous Proposal: [SE-XXXX](XXXX-filename.md)
14+
15+
## Introduction
16+
17+
This proposal aims to add functionality to the standard library for clamping a value to a provided `Range`.
18+
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 then the upper or lower bound would be returned respectively.
19+
20+
Swift-evolution thread: [Add a `clamp` function to Algorithm.swift](https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170306/thread.html#33674)
21+
22+
## Motivation
23+
24+
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 that it was not part of the standard library.
25+
26+
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.
27+
28+
Having functionality like `clamped(to:)` added to `Comparable` as a protocol extension would benefit users of the Swift language who wish
29+
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.
30+
31+
## Proposed solution
32+
33+
The proposed solution is to add a `clamped(to:)` function to the Swift Standard Library as an extension to `Comparable` and to `Strideable`.
34+
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.
35+
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.
36+
37+
Clamping on an empty range simply returns the value clamped to the `lowerBound` / `upperBound` of the `Range` no different from clamping on a non-empty range.
38+
39+
Given a `clamped(to:)` function existed it could be called in the following way, yielding the results in the adjacent comments:
40+
41+
```swift
42+
// Closed range variant
43+
44+
100.clamped(to: 0...50) // 50
45+
100.clamped(to: 200...300) // 200
46+
100.clamped(to: 0...150) // 100
47+
48+
// Half-Open range variant
49+
50+
100.clamped(to: 0..<50) // 49
51+
100.clamped(to: 200..<300) // 200
52+
100.clamped(to: 0..<150) // 100
53+
100.clamped(to: 42..<42) // 42
54+
```
55+
56+
## Detailed design
57+
58+
The implementation of `clamped(to:)` that is being proposed is composed of two protocol extensions; one protocol extension on `Comparable` and another on `Strideable`.
59+
60+
The implementation for `clamped(to:)` as an extension to `Comparable` accepting a range of type `ClosedRange<Self>` would look like the following:
61+
62+
```swift
63+
extension Comparable {
64+
func clamped(to range: ClosedRange<Self>) -> Self {
65+
if self > range.upperBound {
66+
return range.upperBound
67+
} else if self < range.lowerBound {
68+
return range.lowerBound
69+
} else {
70+
return self
71+
}
72+
}
73+
}
74+
```
75+
76+
The implementation of `clamped(to:)` as an extension on `Strideable` would be confined to cases where the stride is of type `Integer`.
77+
The implementation would be as follows:
78+
79+
```swift
80+
extension Strideable where Stride: Integer {
81+
func clamped(to range: Range<Self>) -> Self {
82+
let clampRange: ClosedRange<Self>
83+
84+
if range.lowerBound == range.upperBound {
85+
clampRange = range.lowerBound...range.upperBound
86+
} else {
87+
clampRange = range.lowerBound...(range.upperBound - 1)
88+
}
89+
90+
return clamped(to: clampRange)
91+
}
92+
}
93+
```
94+
95+
## Source compatibility
96+
97+
This feature is purely additive; it has no effect on source compatibility.
98+
99+
## Effect on ABI stability
100+
101+
This feature is purely additive; it has no effect on ABI stability.
102+
103+
## Effect on API resilience
104+
105+
The proposed function would become part of the API but purely additive.
106+
107+
## Alternatives considered
108+
109+
Aside from doing nothing, no other alternatives were considered.

0 commit comments

Comments
 (0)