Extending Generic Integer Types in Swift

3.4k Views Asked by At

So I'm trying to extend Swift's integer types with a few convenient functions that I use a lot, however I'm not clear on which protocols I should be extending.

As an example, let's say I want to implement a function for clamping a value (if it's less than a minimum set it to that, otherwise if it's greater than a maximum then set it to that instead). My first thought was to do something like this:

extension Int {
    func clamp(minimum:Int, maximum:Int) {
        if self < minimum { return minimum }
        if self > maximum { return maximum }
        return self
    }
}

Bit of a simplistic example, but it illustrates the problem; if I want to now call this for a UInt then naturally I can't, so I have to add an equivalent to UInt, but that won't work for a UInt16 and so-on.

I thought that I could perhaps extend something higher up the chain, and use generics instead, however protocols such as IntegerType can't seem to be extended.

So, is there somewhere more appropriate that I could put my extension(s)?

5

There are 5 best solutions below

1
On BEST ANSWER

For Swift 2, see Andriy Gordiychuk's answer, which will be correct then. If you require Swift 1, then this cannot be done with extensions, and must be done with free functions. This is why there are so many free functions in stdlib that became extensions in Swift 2.

For Swift 1, what you have to do is:

func clamp<T:Comparable>(value: T, #minimum:T, #maximum:T) -> T {
    if value < minimum { return minimum }
    if value > maximum { return maximum }
    return value
}

If you prefer modifying the value (as Andriy's example does), you can do it this way:

func clamp<T:Comparable>(inout value: T, #minimum:T, #maximum:T) {
    if value < minimum { value = minimum }
    else if value > maximum { value = maximum }
}

Otherwise you have to write an extension on every type. It's the only other answer in Swift 1. Swift 2 is much better.

0
On

You are on the right track. In fact you are talking about Protocol Oriented Programming.

Protocol extensions: Swift is very focused on protocol-oriented development — there’s even a session on the topic at WWDC 2015. Swift 2.0 adds protocol extensions, and the standard library itself uses them extensively. Where you used to use global functions, Swift 2.0 now adds methods to common types so functions chain naturally, and your code is much more readable.

https://developer.apple.com/swift/blog/?id=29

In fact a big feature of Swift 2.0 is that it allows you to add methods to protocols so you can add clamp to IntegerType.

The video explains very well the topic of Protocol Oriented Programming: https://developer.apple.com/videos/wwdc/2015/?id=408

You just need to upgrade to Swift 2.0.

4
On

While Swift 2.0 is still in beta I suggest you to add extensions like you illustrated. You will have to copy-paste the same code for Int, Int64 etc, but there is no other way to do what you want at the moment.

Once Swift 2.0 is out you will be able to do this

extension IntegerType {
    mutating func clamp(minimum:Self, maximum:Self) {
        if self < minimum { self = minimum }
        if self > maximum { self = maximum }
    }
}

If you can wait with the release of your app until some time in September then I encourage you to start using Swift 2.0 right now.

Update

With Swift 2.0 you can also add extension to Comparable protocol which will ensure that clamp() is available for other types such as Double, Float, etc

extension Comparable {
    mutating func clamp(minimum:Self, maximum:Self) {
        if self < minimum { self = minimum }
        if self > maximum { self = maximum }
    }
}
0
On
extension Comparable {
    func clamp(var minimum: Self, var _ maximum: Self) -> Self {
        if maximum < minimum { swap(&maximum, &minimum) }
        if self < minimum { return minimum }
        if self > maximum { return maximum }
        return self
    }
}
0
On

By way of example, here is an integer implementation of clamped that also applies generically to anything that can use it:

extension Comparable
{
    func clamped(from lowerBound: Self, to upperBound: Self) -> Self {
        return min(max(self, lowerBound), upperBound)
    }

    func clamped(to range: ClosedRange<Self>) -> Self {
        return min(max(self, range.lowerBound), range.upperBound)
    }
}

extension Strideable where Self.Stride: SignedInteger
{
    func clamped(to range: CountableClosedRange<Self>) -> Self {
        return min(max(self, range.lowerBound), range.upperBound)
    }
}

And the test cases:

7.clamped(from: 3, to: 6)   // 6

7.clamped(to: 3 ... 6)      // 6
7.clamped(to: 3 ... 7)      // 7
7.clamped(to: 3 ... 8)      // 7

7.0.clamped(to: 3.0 ... 6.0)  // 6
7.0.clamped(to: 3.0 ... 7.0)  // 7
7.0.clamped(to: 3.0 ... 8.0)  // 7