If val1 > val2, the app will crash with error "Can't form Range with upperBound < lowerBound"
To prevent this, we are forced to check that val1 < val2 every time, which is annoying.
To prevent this, we can make one more clamped(to:) overload:
@John_McCall As mentioned on Github I want to continue the conversation here on this thread before opening a new PR.
I still think this would be a great idea and very useful and there is discussion among community members but not too much back and forth with members of the core team.
Maybe things are a bit more stable now so we can go over this proposal again?
Thoughts on spelling this .bounded(within: some RangeExpression<Self>)?
(Where some RangeExpression<Self> are overloads for every Range type that would make sense to bound a value by, e.g. ClosedRange, PartialRangeFrom, & PartialRangeThrough)
I just came across something potentially confusing/unexpected about this API.
-10.clamped(to: 0...20) // → -10; since this is evaluated as -(10.clamped(to: 0...20))
(-10).clamped(to: 0...20) // → 0; you need to enclose the '-' for the value to be evaluated as -10
By keeping the API on the Range rather than Comparable you avoid this potentially confusing case ((0...20).clamp(-10)). That said, it probably isn't the common case that you'd be working with literals like that so it would still be nice to see this API on Comparable.
A solution there might involve a warning when negative signs are used with in such manner without parens, but we shouldn't be contorting our API designs on an ad hoc basis because of this orthogonal issue (which is also, if not intuitive, still learnable).
Totally agree, and good to know that this a known/orthogonal behavior.
For completeness' sake, the common use case will probably look more like this:
let x = -42
x.clamped(to: 40...50) // 40
(Where the negative sign isn't in play at all, rather the value itself is already negative. So the issue doesn't exist.)
I think a warning would actually make sense, since the transformation to disambiguate is both straightforward and self-explanatory. Another pitch, perhaps.
If we can detect when a prefix - is being used in a chain like this and give a warning, why not just allow the - to be part of the value and not an after-the-fact operator?
Seems to me like the real problem is that we're mixing mathematical expressions with logical operations. In a mathematical expression such as -42.clamped(to: 0...20), the - is part of the value "-42" and the . is some weird syntax (IRL in math. is, depending on your locale, only ever a decimal point or thousand's separator, never an operator) for applying the clamped() function. In code, though, the compiler sees things the other way around... the 42.clamped(to: 0...20) is the value and the - is a prefix operator. Could we "just" not have the compiler do that?
That would be a source-breaking change, unfortunately.
It would also be inconsistent with the rest of the language, where the dot operator is considered higher-precedence than prefix operators. E.g. !value.property for booleans and &instance.member for inout arguments.
That being said, the language does already do some magic with negated literals to allow things like let x: Int8 = -128 without overflow errors.
If I had a time machine, I might require parentheses to disambiguate, but I definitely wouldn't have -x.y mean (-x).y, given the history that John referenced.