clamp() in Swift

22 Jun

I’m writing my newest app entirely in Swift. The language is still so new it’s hard to know what exactly it means to write code in the Swift style. Despite some hurdles with the type safety, like not being able to add a Int8 to Int without first converting one of them, it’s been fun.

I noticed a common theme in part of my code as I was cleaning stuff up today. It goes something like this…

if proposedIndex < 0 {
  return 0
else if proposedIndex < anArray.count-1 {
  return proposedIndex
else {
  return anArray.count-1

This is a pretty standard case of clamping. In the past I’d include my standard math code that handles clamping floats, integers, etc. But we’re in a Swift world now, so let’s take this as an opportunity to learn a bit more about Swift and it’s style.

I figured I could make this clearer. At first I rewrote it like so…

var i = min(proposedIndex, anArray.count-1)
    i = max(proposedIndex, 0)

Not bad, but we lost immutability. Don’t worry, we can get it back and compact it down a bit more at the same time…

let i = min(max(proposedIndex, 0), anArray.count-1)

Well, it’s certainly more compact and quicker to read. But it’s not any clearer. In fact, the compact version is likely worse. But that looks more like C than Swift. So I got to thinking, I wonder what this would look like if it was truly written in Swift style. Lets look at the prototype for min.

func min<T: Comparable>(x: T, y: T) -> T

Ah, of course. It’s using generics. So anything that conforms to the Comparable protocol can be min’ed or max’ed (ya I used them as verbs, what ya going do about it?). Let’s make a function that handles this for us.

func clamp<T: Comparable>(value: T, lower: T, upper: T) -> T {
  return min(max(value, lower), upper)

Now we can use it like so…

let i = clamp(proposedIndex, 0, anArray.count-1)

Now that is both compact and clear. It somewhat surprised me that this isn’t included in the Swift standard lib, seems like a prime candidate for it. Maybe I missed it?

Here is a Gist