[Idea] Add `bounds` function to standard library


(Nicholas Maccharoli) #1

Swift Evolution Community,

In the standard library there is a `min` and a `max` function but there
isn't
a `bounds` function, or a function that takes a value and an upper and
lower bound and returns that value if it is within the specified range or
if not the closer of the lower or upper values supplied to the `bounds`
function.
(Other possible names for such a function could be something like `clamp`,
or `clip`)

I personally see merit in adding a function to bound the value of a
variable within a range and think it would be simple to write with the
existing implementations of `min` and `max` with something like:

    public func bounds<T : Comparable>(value: T, _ lower: T, _ upper: T) ->
T {

        return max(lower, min(value, upper))

    }

Does this sound like something the community thinks would be worthwhile to
add?

If so I would love to write a quick proposal.

- Nick


(Georgios Moschovitis) #2

In the standard library there is a `min` and a `max` function but there isn't
a `bounds` function, or a function that takes a value and an upper and lower bound and returns that value if it is

Perhaps ‘clamp’ would be a better name?


(Pyry Jahkola) #3

I'd welcome that addition. In terms of function interface, I think we can do better than the 3-argument `clamp(x, min, max)` function that is seen in several math libraries.

Our ***Range types already have a `clamped(to:)` member function, e.g. here's one for ClosedRange <https://developer.apple.com/reference/swift/closedrange/1779071-clamped>. It creates a new range constraining the receiver's bounds within the new bounds given as argument.

I think the sensible thing would be to add a similar, and equally named, method to the Comparable protocol, taking in the ClosedRange<Self> to limit the value to:

    extension Comparable {
      public func clamped(to limits: ClosedRange<Self>) -> Self {
        return self < limits.lowerBound ? limits.lowerBound
             : self > limits.upperBound ? limits.upperBound
             : self
      }
    }

    (-0.1).clamped(to: 0 ... 1) // 0.0
    3.14.clamped(to: 0 ... .infinity) // 3.14
    "foo".clamped(to: "a" ... "f") // "f"
    "foo".clamped(to: "a" ... "g") // "foo"

— Pyry

···

On 25 Aug 2016, at 12:05, Nicholas Maccharoli wrote:

I personally see merit in adding a function to bound the value of a variable within a range and think it would be simple to write with the existing implementations of `min` and `max` with something like:

    public func bounds<T : Comparable>(value: T, _ lower: T, _ upper: T) -> T {
        return max(lower, min(value, upper))
    }

Does this sound like something the community thinks would be worthwhile to add?

From my experience, I'd say it'd be most useful for clamping floating-point numbers and collection indices.


(Nicholas Maccharoli) #4

Georgios, Yes lets go with clamp for a name!

Pyry, Originally I thought of just adding a global function akin to `min`
and `max` but I am also
in favour of adding the above extension to `Comparable`.
I think having both the global function and the protocol extension for
`clamp` would be great.

- Nick

···

On Thu, Aug 25, 2016 at 9:37 PM, Pyry Jahkola <pyry.jahkola@iki.fi> wrote:

On 25 Aug 2016, at 12:05, Nicholas Maccharoli wrote:

I personally see merit in adding a function to bound the value of a
variable within a range and think it would be simple to write with the
existing implementations of `min` and `max` with something like:

    public func bounds<T : Comparable>(value: T, _ lower: T, _ upper: T)
-> T {
        return max(lower, min(value, upper))
    }

Does this sound like something the community thinks would be worthwhile to
add?

I'd welcome that addition. In terms of function interface, I think we can
do better than the 3-argument `clamp(x, min, max)` function that is seen
in several math libraries.

Our ***Range types already have a `clamped(to:)` member function, e.g. here's
one for ClosedRange
<https://developer.apple.com/reference/swift/closedrange/1779071-clamped>.
It creates a new range constraining the receiver's bounds within the new
bounds given as argument.

I think the sensible thing would be to add a similar, and equally named,
method to the Comparable protocol, taking in the ClosedRange<Self> to
limit the value to:

    extension Comparable {
      public func clamped(to limits: ClosedRange<Self>) -> Self {
        return self < limits.lowerBound ? limits.lowerBound
             : self > limits.upperBound ? limits.upperBound
             : self
      }
    }

    (-0.1).clamped(to: 0 ... 1) // 0.0
    3.14.clamped(to: 0 ... .infinity) // 3.14
    "foo".clamped(to: "a" ... "f") // "f"
    "foo".clamped(to: "a" ... "g") // "foo"

From my experience, I'd say it'd be most useful for clamping
floating-point numbers and collection indices.

— Pyry


(Tim Vermeulen) #5

What would the point of a free function be if you already have a protocol extension?

···

Georgios, Yes lets go with clamp for a name!

Pyry, Originally I thought of just adding a global function akin to `min`
and `max` but I am also
in favour of adding the above extension to `Comparable`.
I think having both the global function and the protocol extension for
`clamp` would be great.

- Nick

On Thu, Aug 25, 2016 at 9:37 PM, Pyry Jahkola<pyry.jahkola at iki.fi>wrote:

> On 25 Aug 2016, at 12:05, Nicholas Maccharoli wrote:
>
> I personally see merit in adding a function to bound the value of a
> variable within a range and think it would be simple to write with the
> existing implementations of `min` and `max` with something like:
>
> public func bounds<T : Comparable>(value: T, _ lower: T, _ upper: T)
> ->T {
> return max(lower, min(value, upper))
> }
>
> Does this sound like something the community thinks would be worthwhile to
> add?
>
>
> I'd welcome that addition. In terms of function interface, I think we can
> do better than the 3-argument `clamp(x, min, max)` function that is seen
> in several math libraries.
>
> Our ***Range types already have a `clamped(to:)` member function, e.g. here's
> one for ClosedRange
> <https://developer.apple.com/reference/swift/closedrange/1779071-clamped>.
> It creates a new range constraining the receiver's bounds within the new
> bounds given as argument.
>
> I think the sensible thing would be to add a similar, and equally named,
> method to the Comparable protocol, taking in the ClosedRange<Self>to
> limit the value to:
>
> extension Comparable {
> public func clamped(to limits: ClosedRange<Self>) ->Self {
> return self<limits.lowerBound ? limits.lowerBound
> : self>limits.upperBound ? limits.upperBound
> : self
> }
> }
>
> (-0.1).clamped(to: 0 ... 1) // 0.0
> 3.14.clamped(to: 0 ... .infinity) // 3.14
> "foo".clamped(to: "a" ... "f") // "f"
> "foo".clamped(to: "a" ... "g") // "foo"
>
> From my experience, I'd say it'd be most useful for clamping
> floating-point numbers and collection indices.
>
> — Pyry


(Nicholas Maccharoli) #6

Tim,

The protocol extension alone would be sufficient, but for as long as the
global functions
`min` and `max` are still around I thought adding a global clamp function
would make
for good symmetry.

I'll write a small draft proposal to illustrate my idea a little better.

What does the community think?

- Nick

···

On Tue, Aug 30, 2016 at 2:25 AM, Tim Vermeulen <tvermeulen@me.com> wrote:

What would the point of a free function be if you already have a protocol
extension?

> Georgios, Yes lets go with clamp for a name!
>
> Pyry, Originally I thought of just adding a global function akin to `min`
> and `max` but I am also
> in favour of adding the above extension to `Comparable`.
> I think having both the global function and the protocol extension for
> `clamp` would be great.
>
> - Nick
>
>
> On Thu, Aug 25, 2016 at 9:37 PM, Pyry Jahkola<pyry.jahkola at iki.fi > >wrote:
>
> > On 25 Aug 2016, at 12:05, Nicholas Maccharoli wrote:
> >
> > I personally see merit in adding a function to bound the value of a
> > variable within a range and think it would be simple to write with the
> > existing implementations of `min` and `max` with something like:
> >
> > public func bounds<T : Comparable>(value: T, _ lower: T, _ upper: T)
> > ->T {
> > return max(lower, min(value, upper))
> > }
> >
> > Does this sound like something the community thinks would be
worthwhile to
> > add?
> >
> >
> > I'd welcome that addition. In terms of function interface, I think we
can
> > do better than the 3-argument `clamp(x, min, max)` function that is
seen
> > in several math libraries.
> >
> > Our ***Range types already have a `clamped(to:)` member function, e.g.
here's
> > one for ClosedRange
> > <https://developer.apple.com/reference/swift/closedrange/
1779071-clamped>.
> > It creates a new range constraining the receiver's bounds within the
new
> > bounds given as argument.
> >
> > I think the sensible thing would be to add a similar, and equally
named,
> > method to the Comparable protocol, taking in the ClosedRange<Self>to
> > limit the value to:
> >
> > extension Comparable {
> > public func clamped(to limits: ClosedRange<Self>) ->Self {
> > return self<limits.lowerBound ? limits.lowerBound
> > : self>limits.upperBound ? limits.upperBound
> > : self
> > }
> > }
> >
> > (-0.1).clamped(to: 0 ... 1) // 0.0
> > 3.14.clamped(to: 0 ... .infinity) // 3.14
> > "foo".clamped(to: "a" ... "f") // "f"
> > "foo".clamped(to: "a" ... "g") // "foo"
> >
> > From my experience, I'd say it'd be most useful for clamping
> > floating-point numbers and collection indices.
> >
> > — Pyry
>
>
>


(Xiaodi Wu) #7

As an additive proposal, I don't think this would be in scope for the
current phase of Swift 4.

Looking forward, though, I'm not sure this belongs in the standard library.
In general, my understanding is that Swift's standard library is
deliberately small, and that the criteria for additions are that it's
widely used *and* also non-trivial for the user to write correctly. I've
had to use clamping, obviously, but it's a trivial one-liner that is hard
to write incorrectly. If anything, I'd be in favor of removing max and min
into a future math library outside the standard library.

···

On Mon, Aug 29, 2016 at 9:39 PM Nicholas Maccharoli via swift-evolution < swift-evolution@swift.org> wrote:

Tim,

The protocol extension alone would be sufficient, but for as long as the
global functions
`min` and `max` are still around I thought adding a global clamp function
would make
for good symmetry.

I'll write a small draft proposal to illustrate my idea a little better.

What does the community think?

- Nick

On Tue, Aug 30, 2016 at 2:25 AM, Tim Vermeulen <tvermeulen@me.com> wrote:

What would the point of a free function be if you already have a protocol
extension?

> Georgios, Yes lets go with clamp for a name!
>
> Pyry, Originally I thought of just adding a global function akin to
`min`
> and `max` but I am also
> in favour of adding the above extension to `Comparable`.
> I think having both the global function and the protocol extension for
> `clamp` would be great.
>
> - Nick
>
>
> On Thu, Aug 25, 2016 at 9:37 PM, Pyry Jahkola<pyry.jahkola at iki.fi >> >wrote:
>
> > On 25 Aug 2016, at 12:05, Nicholas Maccharoli wrote:
> >
> > I personally see merit in adding a function to bound the value of a
> > variable within a range and think it would be simple to write with the
> > existing implementations of `min` and `max` with something like:
> >
> > public func bounds<T : Comparable>(value: T, _ lower: T, _ upper: T)
> > ->T {
> > return max(lower, min(value, upper))
> > }
> >
> > Does this sound like something the community thinks would be
worthwhile to
> > add?
> >
> >
> > I'd welcome that addition. In terms of function interface, I think we
can
> > do better than the 3-argument `clamp(x, min, max)` function that is
seen
> > in several math libraries.
> >
> > Our ***Range types already have a `clamped(to:)` member function,
e.g. here's
> > one for ClosedRange
> > <
https://developer.apple.com/reference/swift/closedrange/1779071-clamped>.
> > It creates a new range constraining the receiver's bounds within the
new
> > bounds given as argument.
> >
> > I think the sensible thing would be to add a similar, and equally
named,
> > method to the Comparable protocol, taking in the ClosedRange<Self>to
> > limit the value to:
> >
> > extension Comparable {
> > public func clamped(to limits: ClosedRange<Self>) ->Self {
> > return self<limits.lowerBound ? limits.lowerBound
> > : self>limits.upperBound ? limits.upperBound
> > : self
> > }
> > }
> >
> > (-0.1).clamped(to: 0 ... 1) // 0.0
> > 3.14.clamped(to: 0 ... .infinity) // 3.14
> > "foo".clamped(to: "a" ... "f") // "f"
> > "foo".clamped(to: "a" ... "g") // "foo"
> >
> > From my experience, I'd say it'd be most useful for clamping
> > floating-point numbers and collection indices.
> >
> > — Pyry
>
>
>

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


(Karl) #8

min & max (and clamping) are hardly “math” operations. They operate on Comparables, so you can apply them to more abstract things than just numbers.

Otherwise, you might as well put Comparable and all standard numeric types like Int and Float in a math library, too.

···

On 30 Aug 2016, at 10:18, Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:

As an additive proposal, I don't think this would be in scope for the current phase of Swift 4.

Looking forward, though, I'm not sure this belongs in the standard library. In general, my understanding is that Swift's standard library is deliberately small, and that the criteria for additions are that it's widely used *and* also non-trivial for the user to write correctly. I've had to use clamping, obviously, but it's a trivial one-liner that is hard to write incorrectly. If anything, I'd be in favor of removing max and min into a future math library outside the standard library.


(Xiaodi Wu) #9

Comparable makes semantic guarantees about how values of conforming types
might be ordered. You don't need `min` or `max` for that to be useful,
since it's trivial to implement using comparison operators.

Basic numeric types require compiler magic and thus belong in the standard
library. Likewise, dictionaries have special syntactic sugar and have uses
for types that can guarantee comparable semantics. A decimal type, though,
can be implemented outside the standard library and probably would belong
in a math library. Likewise mathematical constants such as e. I think min
and max fall into the latter category.

···

On Wed, Aug 31, 2016 at 8:10 AM Karl <razielim@gmail.com> wrote:

> On 30 Aug 2016, at 10:18, Xiaodi Wu via swift-evolution < > swift-evolution@swift.org> wrote:
>
> As an additive proposal, I don't think this would be in scope for the
current phase of Swift 4.
>
> Looking forward, though, I'm not sure this belongs in the standard
library. In general, my understanding is that Swift's standard library is
deliberately small, and that the criteria for additions are that it's
widely used *and* also non-trivial for the user to write correctly. I've
had to use clamping, obviously, but it's a trivial one-liner that is hard
to write incorrectly. If anything, I'd be in favor of removing max and min
into a future math library outside the standard library.

min & max (and clamping) are hardly “math” operations. They operate on
Comparables, so you can apply them to more abstract things than just
numbers.

Otherwise, you might as well put Comparable and all standard numeric types
like Int and Float in a math library, too.


(Dominik Pich) #10

I agree with karl. there is nothing really mathematical with min/max

e.g. find the longest sequence of characters in a string or the smallest array or the minimal x coordinate of view objects…
min/max/clamp are needed everywhere.

LG
Dominik

Web: https://pich.info
Twitter: @DaijDjan
Facebook: Dominik.Pich

···

On Aug 31, 2016, at 3:53 PM, Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:

Comparable makes semantic guarantees about how values of conforming types might be ordered. You don't need `min` or `max` for that to be useful, since it's trivial to implement using comparison operators.

Basic numeric types require compiler magic and thus belong in the standard library. Likewise, dictionaries have special syntactic sugar and have uses for types that can guarantee comparable semantics. A decimal type, though, can be implemented outside the standard library and probably would belong in a math library. Likewise mathematical constants such as e. I think min and max fall into the latter category.

On Wed, Aug 31, 2016 at 8:10 AM Karl <razielim@gmail.com <mailto:razielim@gmail.com>> wrote:

> On 30 Aug 2016, at 10:18, Xiaodi Wu via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
>
> As an additive proposal, I don't think this would be in scope for the current phase of Swift 4.
>
> Looking forward, though, I'm not sure this belongs in the standard library. In general, my understanding is that Swift's standard library is deliberately small, and that the criteria for additions are that it's widely used *and* also non-trivial for the user to write correctly. I've had to use clamping, obviously, but it's a trivial one-liner that is hard to write incorrectly. If anything, I'd be in favor of removing max and min into a future math library outside the standard library.

min & max (and clamping) are hardly “math” operations. They operate on Comparables, so you can apply them to more abstract things than just numbers.

Otherwise, you might as well put Comparable and all standard numeric types like Int and Float in a math library, too.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Karl) #11

Concrete example I just happened to run across: Foundation’s RunLoopMode conforms to Comparable. It is entirely possible that you may get a collection of RunLoopModes and wish to find the min/max or clamp to a particular mode.

https://github.com/apple/swift-corelibs-foundation/blob/master/Foundation/NSRunLoop.swift

···

On 31 Aug 2016, at 15:53, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

Comparable makes semantic guarantees about how values of conforming types might be ordered. You don't need `min` or `max` for that to be useful, since it's trivial to implement using comparison operators.

Basic numeric types require compiler magic and thus belong in the standard library. Likewise, dictionaries have special syntactic sugar and have uses for types that can guarantee comparable semantics. A decimal type, though, can be implemented outside the standard library and probably would belong in a math library. Likewise mathematical constants such as e. I think min and max fall into the latter category.

On Wed, Aug 31, 2016 at 8:10 AM Karl <razielim@gmail.com <mailto:razielim@gmail.com>> wrote:

> On 30 Aug 2016, at 10:18, Xiaodi Wu via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
>
> As an additive proposal, I don't think this would be in scope for the current phase of Swift 4.
>
> Looking forward, though, I'm not sure this belongs in the standard library. In general, my understanding is that Swift's standard library is deliberately small, and that the criteria for additions are that it's widely used *and* also non-trivial for the user to write correctly. I've had to use clamping, obviously, but it's a trivial one-liner that is hard to write incorrectly. If anything, I'd be in favor of removing max and min into a future math library outside the standard library.

min & max (and clamping) are hardly “math” operations. They operate on Comparables, so you can apply them to more abstract things than just numbers.

Otherwise, you might as well put Comparable and all standard numeric types like Int and Float in a math library, too.


(Xiaodi Wu) #12

Run loop modes are named by string, and as you can see in your link,
comparisons of run loop modes are by their raw value--i.e. by string. While
it's of course sensible to have a total ordering for strings, I'm skeptical
that you would typically want to get the "maximum" of two strings, and I'm
not aware of a use case for clamping a string to a range of strings.

If you're really doing that in your code, you should be aware that the
default ordering for String (or was it just NSString?--my memory is hazy
now) behaves differently on OS X and Linux, at least as of a few months
ago. You really should be using string-specific comparison methods (either
case-sensitive or not) on appropriately normalized strings, in my opinion.

···

On Thu, Sep 1, 2016 at 10:34 PM Karl <razielim@gmail.com> wrote:

On 31 Aug 2016, at 15:53, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

Comparable makes semantic guarantees about how values of conforming types
might be ordered. You don't need `min` or `max` for that to be useful,
since it's trivial to implement using comparison operators.

Basic numeric types require compiler magic and thus belong in the standard
library. Likewise, dictionaries have special syntactic sugar and have uses
for types that can guarantee comparable semantics. A decimal type, though,
can be implemented outside the standard library and probably would belong
in a math library. Likewise mathematical constants such as e. I think min
and max fall into the latter category.

On Wed, Aug 31, 2016 at 8:10 AM Karl <razielim@gmail.com> wrote:

> On 30 Aug 2016, at 10:18, Xiaodi Wu via swift-evolution < >> swift-evolution@swift.org> wrote:
>
> As an additive proposal, I don't think this would be in scope for the
current phase of Swift 4.
>
> Looking forward, though, I'm not sure this belongs in the standard
library. In general, my understanding is that Swift's standard library is
deliberately small, and that the criteria for additions are that it's
widely used *and* also non-trivial for the user to write correctly. I've
had to use clamping, obviously, but it's a trivial one-liner that is hard
to write incorrectly. If anything, I'd be in favor of removing max and min
into a future math library outside the standard library.

min & max (and clamping) are hardly “math” operations. They operate on
Comparables, so you can apply them to more abstract things than just
numbers.

Otherwise, you might as well put Comparable and all standard numeric
types like Int and Float in a math library, too.

Concrete example I just happened to run across: Foundation’s RunLoopMode
conforms to Comparable. It is entirely possible that you may get a
collection of RunLoopModes and wish to find the min/max or clamp to a
particular mode.

https://github.com/apple/swift-corelibs-foundation/blob/master/Foundation/NSRunLoop.swift


(Karl) #13

I think their implementation is wrong — comparing runloop modes by their raw values doesn’t make sense if they are strings. But Runloop is patchy anyway, lots of it still lives in CoreFoundation. I was trying to draw attention to the concept rather than the impl.

Basically, my point is that there are other abstract types other than numbers which can be compared and clamped. A “priority” enum would be an example. Sure, you could make it RawRepresentable and compare/clamp the raw values, but that sounds like a workaround rather than a feature. It may be trivial to implement, but so are min/max themselves.

There’s obviously a line as to what should/shouldn’t be in the standard library. I’m not sure the line is so meandering that it includes min/max while excluding clamp, but it’s all a bit arbitrary anyway ¯\_(ツ)_/¯

···

On 2 Sep 2016, at 06:14, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

Run loop modes are named by string, and as you can see in your link, comparisons of run loop modes are by their raw value--i.e. by string. While it's of course sensible to have a total ordering for strings, I'm skeptical that you would typically want to get the "maximum" of two strings, and I'm not aware of a use case for clamping a string to a range of strings.

If you're really doing that in your code, you should be aware that the default ordering for String (or was it just NSString?--my memory is hazy now) behaves differently on OS X and Linux, at least as of a few months ago. You really should be using string-specific comparison methods (either case-sensitive or not) on appropriately normalized strings, in my opinion.