Stateful mixins in Swift

by Dan Cutting

Last July, Matthijs Hollemans posted a great article about mixins and traits in Swift. He defines three related concepts:

The interface … has method signatures but no actual code

A trait adds code to an interface

A mixin is like a trait but it also has state

Interfaces in Swift are simply protocols, while traits can be made with protocol extensions. Swift doesn’t support mixins directly, but we can get pretty close.

Why mixins?

Think of mixins as building blocks that add behaviour to types.

For example, a Player in a game might need to move. Other entities like an Enemy might also need to move, as well as shoot, have health, etc. We can imagine a variety of behaviours that different entities might need.

Can’t we model these behaviours using inheritance or protocol extensions?

Inheritance could get us some of the way there, but forces us into an inflexible hierarchy which can get ugly pretty quickly if we need to assign arbitrary combinations of behaviours. Multiple inheritance could help, but even speaking those words can make you a pariah. Besides, Swift says no.

Protocol extensions work beautifully for behaviours that don’t require state (since they cannot implement properties). But some behaviours might need to track state, such as a player’s position and speed. In these cases, each type that conforms to the protocol has to implement the properties, leading to duplicated boilerplate code.

Mixins sidestep these problems with the promise of multiple stateful behaviours without being constrained to a single-inheritance hierarchy.

Ultimately, we’d like to just declare the behaviours each of our types have; something like this:

mixin CanMove {
    var position: CGPoint = CGPoint.zero
    var speed: CGVector = CGVector.zero

    mutating func move(forTimeSlice timeSlice: CGFloat) {
        position = CGPoint(x: position.x + timeSlice * speed.dx,
                           y: position.y + timeSlice * speed.dy)
    }
}

mixin HasHealth {
    var health: CGFloat

    mutating func absorb(damage: CGFloat) {
        health -= damage
    }
}

mixin CanShoot { ... }
mixin CanFly { ... }
mixin HasMagic { ... }

struct Player: CanMove, HasHealth, CanShoot {}
struct Enemy: CanMove, CanShoot, CanFly, HasHealth, HasMagic {}

Swift has no mixin keyword (yet), so how can we approximate them?

Building a mixin from scratch

Let’s focus on a CanMove mixin example.

Our very first cut will just put the move function and state directly into the type that needs it.

struct Player {
    var position: CGPoint = CGPoint.zero
    var speed: CGVector = CGVector.zero

    mutating func move(forTimeSlice timeSlice: CGFloat) {
        position = CGPoint(x: position.x + timeSlice * speed.dx,
                           y: position.y + timeSlice * speed.dy)
    }
}

Once we have more than one entity that needs this behaviour, we’ll create a protocol that both entities implement.

protocol CanMove {
    mutating func move(forTimeSlice timeSlice: CGFloat)
}

struct Player: CanMove {
    var position: CGPoint = CGPoint.zero
    var speed: CGVector = CGVector.zero

    mutating func move(forTimeSlice timeSlice: CGFloat) {
        position = CGPoint(x: position.x + timeSlice * speed.dx,
                           y: position.y + timeSlice * speed.dy)
    }
}

struct Enemy: CanMove {
    var position: CGPoint = CGPoint.zero
    var speed: CGVector = CGVector.zero

    mutating func move(forTimeSlice timeSlice: CGFloat) {
        position = CGPoint(x: position.x + timeSlice * speed.dx,
                           y: position.y + timeSlice * speed.dy)
    }
}

We can at least now talk about both of these types as things that can move.

var movingThings: [CanMove] = [Player(), Enemy()]

for var thing in movingThings {
    thing.move(forTimeSlice: 1.0)
}

But the implementation of the behaviour is duplicated, so let’s extract that to a struct and trampoline the calls on from each of our entities.

protocol CanMove {
    mutating func move(forTimeSlice timeSlice: CGFloat)
}

struct CanMoveMixin {
    var position: CGPoint = CGPoint.zero
    var speed: CGVector = CGVector.zero

    mutating func move(forTimeSlice timeSlice: CGFloat) {
        position = CGPoint(x: position.x + timeSlice * speed.dx,
                           y: position.y + timeSlice * speed.dy)
    }
}

struct Player: CanMove {
    var canMoveMixin: CanMoveMixin = CanMoveMixin()

    mutating func move(forTimeSlice timeSlice: CGFloat) {
        canMoveMixin.move(forTimeSlice: timeSlice)
    }
}

struct Enemy: CanMove {
    var canMoveMixin: CanMoveMixin = CanMoveMixin()

    mutating func move(forTimeSlice timeSlice: CGFloat) {
        canMoveMixin.move(forTimeSlice: timeSlice)
    }
}

This is much better. The Player and Enemy still both conform to CanMove but we don’t duplicate the mixin code.

The downside is that we end up with a lot of boilerplate. As we create new entities that CanMove, or as we add more functions to the CanMove mixin itself, we’ll need masses of corresponding trampoline functions.

Is there any way to avoid this?

Injected boilerplate!

The trick is to employ protocol extensions to inject the same boilerplate trampoline functions into every entity that conforms to our mixin protocol.

The Player and Enemy structs will still need to store a reference to the stateful mixin struct, but all that forwarding code can be written just once for each mixin (in a protocol extension), rather than in every entity that uses the mixin.

protocol CanMove {
    mutating func move(forTimeSlice timeSlice: CGFloat)
}

extension CanMove {
    mutating func move(forTimeSlice timeSlice: CGFloat) {
        canMoveMixin.move(forTimeSlice: timeSlice)
    }
}

struct CanMoveMixin { ... }

struct Player: CanMove {
    var canMoveMixin: CanMoveMixin = CanMoveMixin()
}

struct Enemy: CanMove {
    var canMoveMixin: CanMoveMixin = CanMoveMixin()
}

But this approach doesn’t quite work yet. The extension isn’t able to find a reference to the canMoveMixin property in the Player and Enemy structs since it only knows about what’s in the CanMove protocol itself.

Fortunately, we can declare the canMoveMixin property in the protocol. Player and Enemy already implement this property, so the extension can now look them up.

protocol CanMove {
    var canMoveMixin: CanMoveMixin { get set }
    mutating func move(forTimeSlice timeSlice: CGFloat)
}

extension CanMove {
    mutating func move(forTimeSlice timeSlice: CGFloat) {
        canMoveMixin.move(forTimeSlice: timeSlice)
    }
}

struct CanMoveMixin {
    var position: CGPoint = CGPoint.zero
    var speed: CGVector = CGVector.zero

    mutating func move(forTimeSlice timeSlice: CGFloat) {
        position = CGPoint(x: position.x + timeSlice * speed.dx,
                           y: position.y + timeSlice * speed.dy)
    }
}

struct Player: CanMove {
    var canMoveMixin: CanMoveMixin = CanMoveMixin()
}

struct Enemy: CanMove {
    var canMoveMixin: CanMoveMixin = CanMoveMixin()
}

Success! We can now create new types that mixin the stateful CanMove behaviour with just one additional line of code per entity. It’s not perfect but it’s pretty close to our goal.

Using this same pattern, we can stamp out new mixins with ease.

protocol HasHealth {
    var hasHealthMixin: HasHealthMixin { get set }
    mutating func absorb(damage: CGFloat)
}

extension HasHealth {
    mutating func absorb(damage: CGFloat) {
        hasHealthMixin.absorb(damage: damage)
    }
}

struct HasHealthMixin {
    var health: CGFloat

    mutating func absorb(damage: CGFloat) {
        health -= damage
    }
}

struct Player: CanMove, HasHealth {
    var canMoveMixin: CanMoveMixin = CanMoveMixin()
    var hasHealthMixin: HasHealthMixin = HasHealthMixin()
}

struct Enemy: CanMove {
    var canMoveMixin: CanMoveMixin = CanMoveMixin()
}

Conclusion

Mixins are a very powerful modelling tool that let us compose our types with stateful behaviour and no fixed inheritance hierarchy.

Until Swift gets real mixins, this is a fairly tidy way to approximate them. If you have ideas on how they could be improved further, let me know on Twitter @dcutting.

Hi, I'm Dan Cutting. I mainly write Swift for iOS and Mac but love playing with Metal and programming languages too.

Follow me on Twitter or GitHub or subscribe to the feed.