Add a `clamp` function to Algorithm.swift

Hi Nicholas --

Thanks for pushing on this a bit. I think that this would be a great thing to have in the standard library, and the cleaned up proposal is definitely an improvement. I do think that some tweaks and additional discussion is necessary to proceed, however:

First, the proposal as it stands doesn't spell out what happens if a type has "exceptional values" for Comparable (i.e. NaN). The behavior is implicitly specified by the definition that you give, but it would be good to spell it out explicitly in the detailed design section:

  • You cannot make a closed range with NaN as a bound, so we don't need to worry about that case.

  • You can make a partial range with a NaN bound, but this is probably a bug that should be fixed.

  • If a is NaN, then a.clamped(to: range) returns NaN with the definition you have. There are two competing considerations here; on one hand, clamped should satisfy the postcondition that the result is in the specified range, but on the other hand, floating-point operations generally should produce NaN if their inputs are NaN. There are three defensible options that do not require elaborate and far-reaching changes to the type hierarchy of the standard library:

    • return .nan
    • return range.lowerBound or range.upperBound
    • preconditionFailure

    The proposal should choose one of these and document it, and discuss the others under "alternatives considered", and the eventual implementation in the PR should add test cases that cover this.

My own (weak) choice would be to make this a preconditionFailure, but also to add a median free function that lets you get the second behavior (a.clamp(to: b ... c) is precisely median(a,b,c) in the non-exceptional case, and it's easier to reason about what the semantics should be in the exceptional cases, plus it's a generally useful operation that I think we should have in the standard library).

10 Likes