Extending Generic Integer Types in Swift
Asked Answered
G

5

10

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)?

Gilboa answered 11/8, 2015 at 13:55 Comment(11)
Instead of extending a type that everyone is already familiar with you should wrap the Integer with your own class or put your frequently used methods in a static math util classSalangia
Isn't extension specifically intended as an alternative to this? I thought that I could just put my function(s) into an extension of say IntegerType using generics and this way it would appear for all integer values. This seems much neater to me than an IntUtils class with a ton of static functions in it.Gilboa
The argument for not doing this is that there is a well know interface for the low level types that all programmers in a particular language are going to know. If I know what can be done with an Integer in one project I should know what can be done with an Integer in another project.Salangia
@bhspencer: I don't see the problem. If you change project and you miss some extensions... just import them to the new one! :-) Extensions are a big part of iOS development. Before Swift, in Objective-C, they were called Categories. Please just read the official documentation before suggesting not to use them... You'll find lots of scenarios where this technique is very useful.Wayne
@appzYourLife I think the root of the issue is that extending a low level type breaks the principle of "separation of concerns". An Integer should only be concerned with Integer related things. You could extend Integer in Swift to add functions specific to your problem domain e.g. if your domain is tennis you extend Integer with a function that adds 15 or 10 points to help keep score. In such a case a better solution would be to have a class TennisScore that wraps an Integer rather than extending Integer.Salangia
@bhspencer: Of course an increaseScore function would NOT be a good extension for the Int struct. I think indeed the scenario you described is a clear example where extensions should NOT be used. On the other hand clamp is something related to the Int. So it would be a good example of extension for the Int struct. Even better in Swift 2.0 we can extend protocols so we can add the clamp function to the Comparable protocol. This allows us to define clamp at an higher level. And it’s beautiful because as soon as we conform to Comparable we get the clamp method for free!...Wayne
IMHO this is much better then the Utils class witch static methods you were suggesting previously (it looks more a solution for Java). Take a look at the changes Apple has done from Swift 1.2 to Swift 2.0. It has taken several globally declared functions and has moved them to protocols using extensions. Like split that in Swift 2.0 is implemented inside an extension of the protocol CollectonType ...Wayne
Swift is a protocol oriented programming and the extensions are a big part of this philosophy. Please just take a look a this developer.apple.com/videos/wwdc/2015/?id=408 I am sure it does explain these concepts much better then me:Wayne
Honestly a bunch of extensions is not much better than a "Utils class with static methods" -- in either case you end up with a file full of utility methodsSorci
What @Salangia suggested isn't a "Utils class". He suggested modeling the data of the application using composition (e.g Int as a variable) in order to adhere to the principle of separation of concerns (ie. don't put unnecessary logic into Int when you don't need to)Sorci
And there is the added issue of API discoverability that bhspencer also raised. If you model your application domain, then each model's methods are easily discoverable. If you instead use extensions, how are your collaborators to know to use Int.clamp?Sorci
H
8

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.

Hideous answered 11/8, 2015 at 15:9 Comment(1)
Looks like this is the correct answer for now; fortunately I'm not pressed for time, so I can just make do for now and revisit it when Swift 2 comes along, thanks for the answer!Gilboa
P
5

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 }
    }
}
Phago answered 11/8, 2015 at 15:3 Comment(4)
Note that for the given example, where clamp returns a value rather than mutating, this is better applied to Comparable rather than IntegerType.Hideous
That is true, but original function did not declare return type (although it did contain "return" in its body). So, I assumed that 1) @Gilboa wanted to extend integer type only (which seems the case) and 2) that it's easier, for this purpose, to use a.clamp() rather than a = a.clamp()Phago
On second thought, whether it returns or not also doesn't matter. It can still be Comparable even in your version.Hideous
I've marked @RobNapier's answer as correct for now, but it does sound like Swift 2's changes are much more in line with what I need. Also, my clamp function was just as a quick and dirty example, many thanks for sticking with it, but yeah, you're absolutely right that it's probably a better example for Comparable!Gilboa
A
2

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
Arabel answered 3/5, 2017 at 20:51 Comment(0)
W
1

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.

Wayne answered 11/8, 2015 at 14:15 Comment(0)
G
1
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
    }
}
Garbanzo answered 8/11, 2015 at 9:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.