【问题标题】:Problem with associtedtype when referring to a protocol metatype引用协议元类型时关联类型的问题
【发布时间】:2020-11-15 21:37:43
【问题描述】:

这是我的问题:

假设我有一个协议,其中associatedtype 指代它的元类型:

protocol TestMeta {
    associatedtype T
    
    var x : T.Type { get }
    var y : T { get }
}

如果我创建一个具体类型的结构,没问题:

struct AMeta : TestMeta {
    var x : Int.Type
    var y : Int
}

但是如果associatedtype 指的是协议,我得到了 “Type 'BMeta' does not conform to protocol 'TestMeta'” 错误:

protocol P { }

struct BMeta : TestMeta {
    var x : P.Type
    var y : P
}

(即使我添加了typealias 定义来帮助推理引擎)

当然,如果我不引用元类型,一切都适用于具体的类型和协议,即使我有其他未在协议中定义的元类型变量:

protocol TestNoMeta {
    associatedtype T
    
    var z : T { get }
}

struct ANoMeta : TestNoMeta {
    var z : Int
    var t : Int.Type
}

struct BNoMeta : TestNoMeta {
    var z : P
    var t : P.Type
}

如果有人能解释我做错了什么?我怎样才能实现我的目标?提前致谢。

编辑:但正如@New Dev 指出的那样,我没有解释我在寻找什么。我希望能够做这样的事情:

struct S : P { }

let b = BMeta(x: S.self, y: S())

知道它仍在使用 NoMeta 协议进行编译:

let bb = BNoMeta(z: S(), t: S.Type) 

EDIT 2:最后我希望做这样的事情:

protocol P { 
    init() 
}

extension TestMeta {
    func build() -> P {
        return x.init()
    }
}

struct BMeta : TestMeta {
    var x : P.Type
    var y : P
}

struct S : P { }

let b = BMeta(x: S.self, y: S()) 
let c = b.build()

编辑 3:好的,好的,这是我的真实用例,我认为简化事情会更好,但似乎不是......

protocol Initializable {
    init()
}

protocol OptionListFactory {
    associatedtype Option : Initializable
    static var availableOptions: [Option.Type] { get }
}

extension OptionListFactory {
    static func build(code: Int) -> Option? {
        if code >= 0 && code < availableOptions.count {
            return availableOptions[code].init()
        }
        else {
            return nil
        }
    }
}

protocol Contract : Initializable {
    ...
}

struct Contract01 : Contract { ... }
struct Contract02 : Contract { ... }
...
struct Contract40 : Contract { ... }

struct ContractFactory : OptionListFactory {
    static let availableOptions: [Contract.Type] = [
        Contract01.self,
        Contract02.self,
        ...        
        Contract40.self,
    ]
}

protocol Element : Initializable {
    ...
}

struct Element01 : Element { ... }
struct Element02 : Element { ... }
    ...
struct Element20 : Element { ... }

struct ElementFactory : OptionListFactory {
    static let availableOptions: [Element.Type] = [
        Element01.self,
        Element02.self,
        ...
        Element20.self,
    ]
}

希望你能更好地理解我的目的......

【问题讨论】:

  • 不确定您要实际实现的目标,但要将其编译为 var x : P.Protocol
  • 不幸的是,这并不意味着相同。例如,如果我使用您的解决方案创建一个符合 P: struct S : P { } 的结构,我不能这样做:let b = BMeta(x: S.self, y: S()) 因为编译器无法将 S.Type 转换为 P.Protocol
  • 将协议称为类型的元是很奇怪的,元应该是具体的,而不是抽象的。为什么你需要这个设计,你的最终目标是什么?
  • @Cristik IRL, P 将包含 init() 用于构建实例的扩展。
  • 为什么不在这里使用泛型?

标签: swift generics protocols associated-types


【解决方案1】:

这是标准的“协议不符合协议”问题。 Contract 是一个协议,它要求符合类型符合 Initializable。它不是本身可初始化的。事实上,这是一个典型的例子,说明为什么协议在最一般的情况下不能符合自己。如果是这样,我可以写Contract.init() 并回复...什么?没有办法为“抽象合约”分配内存。

由于Contract不符合Initializable,所以不能直接是Option。今天的 Swift 也缺乏任何机制来讨论协议继承。您不能说 Option 必须是需要 Initializable 的协议。它超越了 Swift 类型系统。

也就是说,这是一种相当不灵活的方法。它要求每种类型都接受一个琐碎的init,这对可以符合的类型非常有限。 IMO 有更好的方法来实现这种工厂。

摆脱 Initializable,摆脱 static 要求(稍后您会看到原因),并将类型替换为构建器函数。

protocol OptionListFactory {
    associatedtype Option
    var availableOptions: [() -> Option] { get }
}

这会导致以这种方式调整扩展:

extension OptionListFactory {
    // Remove `static`
    func build(code: Int) -> Option? {
        if code >= 0 && code < availableOptions.count {
            return availableOptions[code]()  // Call the builder function
        }
        else {
            return nil
        }
    }
}

还有一个简单的工厂:

protocol Contract {}

struct Contract01 : Contract {}
struct Contract02 : Contract {}

struct ContractFactory : OptionListFactory {
    let availableOptions: [() -> Contract] = [
        Contract01.init,   // init rather than self
        Contract02.init,
    ]
}

但是,如果您想出售某种类型,需要一些额外的信息来构建呢?这样做很简单。

// Elements need a name
protocol Element {
    var name: String { get }
}

struct Element01 : Element { let name: String }
struct Element02 : Element { let name: String }
struct Element20 : Element { let name: String }

struct ElementFactory : OptionListFactory {
    let availableOptions: [() -> Element]

    // And now we can assign that name
    init(prefix: String) {
        availableOptions = [
            { Element01(name: prefix + "01") },
            { Element02(name: prefix + "02") },
            { Element20(name: prefix + "20") },
        ]
    }
}

let ef = ElementFactory(prefix: "local")
let e20 = ef.build(code: 2)
// e20?.name == "local20"

通过制作工厂实例而不是使用static,它们更加灵活并且可以配置。通过使用函数,您可以以您可能没有考虑过的方式进行创作。例如,您可以在每次创建选项时添加调试打印语句:

struct DebugFactory<Base: OptionListFactory>: OptionListFactory {
    let availableOptions: [() -> Base.Option]

    init(base: Base) {
        availableOptions = base.availableOptions.map { f in
            return {
                print("Creating object")
                return f()
            }
        }
    }
}

// Works exactly the same as `ef`, just prints.
let debugFactory = DebugFactory(base: ef)
debugFactory.build(code: 2)

【讨论】:

  • 该死的你真棒!一千个谢谢你!!!这对我来说很清楚!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-16
  • 1970-01-01
  • 2018-07-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多