Is there a way to constrain `Self` to a generic type?

时光毁灭记忆、已成空白 提交于 2019-12-11 05:18:27

问题


https://www.raywenderlich.com/148448/introducing-protocol-oriented-programming

protocol Bird {
    var name: String { get }
    var canFly: Bool { get }
    func doSomething()
}

protocol Flyable {
    var airspeedVelocity: Double { get }
}

extension Bird {
    // Flyable birds can fly!
    var canFly: Bool { return self is Flyable }
    func doSomething() {
        print("default Bird: \(name)")
    }
}

class FlappyBird: Bird, Flyable {
    let name: String
    let canFly = true
    var airspeedVelocity: Double = 5.0

    init(name: String) {
        self.name = name
    }
}

class Penguin: Bird {
    let name: String
    let canFly = false

    init(name: String) {
        self.name = name
    }
}

class Owl<T> : Bird {
    let name: String
    let power: T

    init(name: String, power: T) {
        self.name = name
        self.power = power
    }
}

extension Bird where Self: FlappyBird {
    func doSomething() {
        print("FlappyBird: \(name)")
    }
}

extension Bird where Self: Owl<String> {
    func doSomething() {
        print("Owl<String>: \(name)")
    }
}

    let fb = FlappyBird(name:"PAK")
    let penguin = Penguin(name:"Mr. Pickle")
    let nightOwl = Owl<String>(name:"Night Owl", power:"Who")
    let dayOwl = Owl<Int>(name:"Day Owl", power: 50)

    let birds: [Bird] = [fb, penguin, nightOwl, dayOwl]

    birdloop: for bird in birds {
        bird.doSomething()
    }

The output I get is:

FlappyBird: PAK
default Bird: Mr. Pickle
default Bird: Night Owl
default Bird: Day Owl

The first result works as expected since

extension Bird where Self: FlappyBird {
    func doSomething() {
        print("FlappyBird: \(name)")
    }
}

The second result works as expected since it calls the default protocol extension:

extension Bird {
        // Flyable birds can fly!
        var canFly: Bool { return self is Flyable }
        func doSomething() {
            print("default Bird: \(name)")
        }
    }

The third result I would expect to print

Owl<String>: Night Owl

since nightOwl is of type Owl<String>. But instead it calls the default protocol extension:

default Bird: Night Owl

Is there some reason why

extension Bird where Self: FlappyBird {
  func doSomething() {
    print("default Bird: \(name)")
  }
}

is called for the FlappyBird type but

extension Bird where Self: Owl<String> {
  func doSomething() {
    print("Owl<String>: \(name)")
  }
}

is not called for the Owl<String> type?


回答1:


For the generic type Owl<T> you are allowed to have the constraint where Self: Owl<String>, but it will only work in contexts where the concrete type information is available.

To make it clear what is happening, consider this:

let nightOwl = Owl<String>(name: "Night Owl", power: "Who")
nightOwl.doSomething() // prints "Owl<String>: Night Owl"

As opposed to this:

let nightOwl: Bird = Owl<String>(name: "Night Owl", power: "Who")
nightOwl.doSomething() // prints "default Bird: Night Owl"

When Swift creates the protocol witness tables for the types Owl<T> and FlappyBird, it has to act differently for each one because Owl is generic. If it doesn't have the concrete type information, i.e. Owl<String> at the call site, it must use the default implementation for Owl<T>. This "loss" of type information is happening when you are inserting the owls into the array of type [Bird].

In the case of FlappyBird, since there is only one possible implementation (since it's not generic), the compiler produces a witness table with the "expected" method reference, which is print("FlappyBird: \(name)"). Since FlappyBird is not generic, its witness table doesn't need any reference to the unconstrained default implementation of doSomething() and can therefore correctly call the the constrained implementation even when the concrete type information is missing.

To make it clear that the compiler "needs" to have the fall back behavior for a generic type, you can remove the Bird conformance from Owl<T> and try to rely solely on the constrained default implementation. This will result in a compilation error with an error that is, as usual with Swift, highly misleading.

Value of type 'Owl' has no member 'doSomething'

Basically, it seems the witness table can't be built because it requires the existence of an implementation that will work for all types T on Owl.

References

  1. https://forums.swift.org/t/swifts-method-dispatch/7228
  2. https://developer.apple.com/videos/play/wwdc2016/416/



回答2:


@AllenHumphreys answer here is a decent explanation on the why part.

As for the fix part, implement doSomething() for the generic Owl as:

class Owl<T> : Bird {
    //...
    func doSomething() {
        print("Owl<\(type(of: power))>: \(name)")
    }
}

and now you no longer need doSomething() in extension Bird where Self: Owl<String>



来源:https://stackoverflow.com/questions/49816045/is-there-a-way-to-constrain-self-to-a-generic-type

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!