dented42
dented42

Reputation: 338

Implementing protocol static method that takes and returns Self (in swift)

Given a protocol something along these lines:

protocol Thing {
  static func *(lhs: Float, rhs: Self) -> Self
}

How is one expected to implement a box class?

class ThingBox<T: Thing>: Thing {
  var thing: T
  required init(thing: T) { self.thing = thing }
  static func *(lhs: Float, rhs: Self) -> Self {
    return Self(thing: lhs * rhs.thing)
  }
}

The compiler complains that Self cannot be used in the method type, however the fact that ThingBox is subclassable means that using ThingBox<T> isn't appropriate.

Is it not possible to write this class without forcing it to be final?

Upvotes: 2

Views: 899

Answers (1)

Rob Napier
Rob Napier

Reputation: 299265

Your * implementation has a few subtle problems. Here's the implementation you mean:

static func *(lhs: Float, rhs: ThingBox<T>) -> Self {
    return self.init(thing: lhs * rhs.thing)
}

First, you can't use Self as a parameter type. You have to be explicit. Self means "the actual type" and if you could use that for a subclass, this would violate LSP. For example, say I have types Animal and Dog (with the obvious relationships). Say I wrote the function:

class Animal {
    func f(_ a: Self) { ... }
}

with the meaning that Animal.f will take Animal, but Dog.f will only take Dog but will not take a Cat. So you would expect the following to be true:

dog.f(otherDog) // this works
dog.f(cat) // this fails

But that's breaks the rules of substitution. What if I write this:

let animal: Animal = Dog()
animal.f(cat)

That should be legal, because Animal.f can take any Animal, but the implementation of Dog.f cannot take a cat. Type mismatch. Boom. So it's not legal. (This restriction does not exist for return types. I'll leave that as an exercise for the reader. Try to create an example like the above for returning Self.)

The second error was just a syntax mistake. It's not Self(), it's self.init(). In a static method, self is the dynamic type (which is what you want), and Swift requires you to call init explicitly when used this way. That's just syntax, not a deep type issue like the other.


There's no way in Swift to inherit the thing you're talking about. If you want to overload, that's fine, but you have to be explicit about the types:

class OtherBox: ThingBox<Int> {
    static func *(lhs: Float, rhs: OtherBox) -> Self {
        return self.init(thing: lhs * rhs.thing)
    }
}

This does exactly what you want, but it has to be added to each child; it won't inherit automatically with covariance. Swift doesn't have a strong covariance system to express it.

That said, when you start intermixing generics, protocols, and subclassing this way, you're going to run into many, many weird corner cases, both due to the math, and due to current Swift limitations. You should ask carefully if your actual code needs this much parameterization. Most cases I encounter of these kinds of questions are over-designed "in case we need it" and just simplifying your types and making things concrete is everything you need for solving the actual program you want to write. It's not that it wouldn't be nice to build incredibly generic algorithms built on higher-kinded types, but Swift just isn't the language for that today (and possibly never; there are a lot of costs to adding those features).

Upvotes: 2

Related Questions