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

TylerH
  • 20,799
  • 66
  • 75
  • 101
Haravikk
  • 3,109
  • 1
  • 33
  • 46
  • 1
    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 class – bhspencer Aug 11 '15 at 14:03
  • 2
    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. – Haravikk Aug 11 '15 at 14:31
  • 1
    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. – bhspencer Aug 11 '15 at 14:48
  • 1
    @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. – Luca Angeletti Aug 11 '15 at 15:02
  • 1
    @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. – bhspencer Aug 12 '15 at 15:15
  • @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!... – Luca Angeletti Aug 12 '15 at 16:56
  • 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 `... – Luca Angeletti Aug 12 '15 at 16:56
  • Swift is a protocol oriented programming and the extensions are a big part of this philosophy. Please just take a look a this https://developer.apple.com/videos/wwdc/2015/?id=408 I am sure it does explain these concepts much better then me: – Luca Angeletti Aug 12 '15 at 16:56
  • 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 methods – Ilias Karim Nov 04 '15 at 20:55
  • 1
    What @bhspencer 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) – Ilias Karim Nov 04 '15 at 20:58
  • 1
    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? – Ilias Karim Nov 04 '15 at 21:00

5 Answers5

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.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • 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! – Haravikk Aug 12 '15 at 13:36
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 }
    }
}
Community
  • 1
  • 1
Andriy Gordiychuk
  • 6,163
  • 1
  • 24
  • 59
  • Note that for the given example, where `clamp` returns a value rather than mutating, this is better applied to `Comparable` rather than `IntegerType`. – Rob Napier Aug 11 '15 at 15:13
  • That is true, but original function did not declare return type (although it did contain "return" in its body). So, I assumed that 1) @Haravikk 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() – Andriy Gordiychuk Aug 11 '15 at 15:16
  • On second thought, whether it returns or not also doesn't matter. It can still be Comparable even in your version. – Rob Napier Aug 11 '15 at 15:23
  • 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`! – Haravikk Aug 12 '15 at 13:39
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
William Entriken
  • 37,208
  • 23
  • 149
  • 195
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.

TylerH
  • 20,799
  • 66
  • 75
  • 101
Luca Angeletti
  • 58,465
  • 13
  • 121
  • 148
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
    }
}
user3441734
  • 16,722
  • 2
  • 40
  • 59