Exploring Swift’s Generics: Part Deux

Unlike the Mel Brooks movie, History of the World, Part I, the “Exploring Swift’s Generics: Part 1” post will get its sequel. Since I deferred the discussion of protocols, that will be the subject of Part II.

Here is the code that we’ll reuse from the previous example:

class Magic {
    func cast() -> String {
        return ""
    }
}

class BlackMagic: Magic {
    override func cast() -> String {
        return ""
    }
}

class WhiteMagic: Magic {
    override func cast() -> String {
        return ""
    }
}

class Fire: BlackMagic {
    override func cast() -> String {
        return "Fire!"
    }
}

class Ice: BlackMagic {
    override func cast() -> String {
        return "Ice!"
    }
}

class Cure: WhiteMagic {
    override func cast() -> String {
        return "Cure!"
    }
}

class Antidote: WhiteMagic {
    override func cast() -> String {
        return "Antidote!"
    }
}

 
The thing that I found strangest about trying to implement a generic protocol is that you can’t. Protocols use a different syntax altogether. When working with protocols, we’ll use what are called “associated types” to achieve generic-like functionality via the typealias keyword.

Here is how we define the protocol (“mage protocol” sounds pretty awesome, doesn’t it?):

protocol MageProtocol {
    typealias MagicType: Magic

    func castAll(items: [MagicType])
}

 
Notice that we are able to specify a constraint for our type alias, which will come in handy.

Here is one way we could use our protocol to create a mage that only casts black magic:

class BlackMage: MageProtocol {
    typealias MagicType = BlackMagic
    
    func castAll(items: [BlackMagic]) {
        for item in items {
            println(item.cast())
        }
    }
}

BlackMage().castAll([Fire(), Ice()])

 
One shortcoming, in my opinion, of inheriting from a generic class in Swift is that the subclass has to be generic as well. In the case of the protocol, however, there is no need for a generic.

That is not to say that we cannot take advantage of generics:

class Mage<T: Magic> : MageProtocol {
    typealias MagicType = T
    func castAll(items: [T]) {
        for item in items {
            println(item.cast())
        }
    }
}

Mage<WhiteMagic>().castAll([Cure()])

 
Pay attention to the fact that I had to specify a constraint for the generic type that was compatible with the protocol’s type alias.

It seems to me that implementing a protocol with associated types, rather than inheriting from a generic base class, is the cleaner solution (review “Exploring Swift’s Generics: Part 1” for examples of inheriting from a generic class causing weirdness.)

Happy swifting…

Share