Extensions with Generic Where clauses in Swift

Require the specific element in question to be a specific type.

Protocols are extremely important in Swift. Equally Extensions offer flexibility and allow us to add functionality to both Objects and Protocols — but here is an interesting way in which these tools can be combined in Swift.

Image for post
Image for post
Photo by Andrew Neel on Unsplash

Difficulty: Beginner | Easy | Normal | Challenging

Prerequisites:

  • Be able to produce a “Hello, World!” iOS application (guide HERE)
  • Knowledge of extensions in Swift (guide HERE)
  • Preferably have some knowledge of Swift’s comparable protocol (guide HERE)

Terminology

Extensions: Extensions add new functionality to a class, struct, enum or protocol

Generics: Reusable functions and types that can work with any type (or subset of a certain type

The example

Imagine we want to have entries on a Leaderboard. In order to do so, we decide to create a Class to represent each entry on the leaderboard.

Image for post
Image for post
Photo by Leslie Jones on Unsplash
class LeaderboardScoreEntry {
let score: Int
init(score: Int) {
self.score = score
}
}

now we can create some scores, and put them into an array:

let lowScore = LeaderboardScoreEntry(score: 2)
let highScore = LeaderboardScoreEntry(score: 8)
[highScore, lowScore]

Now the challenge with which we are going to demonstrate the Generic Where Clause, that is we are going to sort our LeaderboardScoreEntry objects in ascending order of scores.

Working towards the solution

Please note that most of the code in this section will not compile. For the solution with the where clause skip to the “The Solution” section of this article.

It makes sense that we should put the sort function into an extension:

extension Array<Element>{
func sort() -> [Element] {
return sorted(by: {$0 < $1})
}
}

Now this gives us a particular error:

Constrained extension must be declared on the unspecialized generic type ‘Array’ with constraints specified by a ‘where’ clause

which, translated, rather means that we need a where clause. No problem; we will make sure that each element of the array will be comparable

extension Array where Element: Comparable {
func sort() -> [Element] {
return sorted(by: {$0 < $1})
}
}

Now of course, our LeaderboardScoreEntry needs to conform to the Comparable protocol as this is precisely what we have specified.

class LeaderboardScoreEntry {
let score: Int
init(score: Int) {
self.score = score
}
static func == (lhs: LeaderboardScoreEntry, rhs: LeaderboardScoreEntry) -> Bool {
return lhs.score == rhs.score
}
static func < (lhs: LeaderboardScoreEntry, rhs: LeaderboardScoreEntry) -> Bool {
return lhs.score < rhs.score
}
}

which we can then call using the following:

let lowScore = LeaderboardScoreEntry(score: 2)
let highScore = LeaderboardScoreEntry(score: 8)
let sortedArray = [highScore, lowScore].sort()

This final type is sorted in ascending order.

Now (and repeat this yourself); there is nothing wrong with this approach. One thing, though, is that there are many lines of code — particularly those extra lines to make LeaderboardScoreEntry conform to Comparable. Now this might make sense if you need the class to be comparable for some other reason this might be a fantastic solution.

If not, there is a solution with rather less code…

The Solution: An extension with where

We can solve this problem by creating an extension to our array type, but let us make it only apply when the element is of type LeaderboardScoreEntry.

extension Array where Element: LeaderboardScoreEntry {
func sort() -> [LeaderboardScoreEntry] {
return sorted(by: {$0.score < $1.score})
}
}

which is then called with our custom sort that has been declared within the extension.

let lowScore = LeaderboardScoreEntry(score: 2)
let highScore = LeaderboardScoreEntry(score: 8)
let sortedArray = [highScore, lowScore].sort()

This final type is sorted in ascending order.

Note how tidy the code is people: this is the solution you are looking for.

Conclusion…

The example above is really quite trivial, however this is a really powerful tool in Swift.

This article has given you two approaches to this problem, and hopefully some idea of how to choose between the two.

Using the where clause, effectively you are prescribing what type the element will be that the extension applies to. This is awesome, and enables us to take interesting and effective approaches in our code.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store