引用协议元类型时关联类型的问题
Problem with associtedtype when referring to a protocol metatype
这是我的问题:
假设我有一个协议 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)
编辑 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,
]
}
希望你能更好地理解我的目标...
这是标准的“协议不符合协议”问题。 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)
这是我的问题:
假设我有一个协议 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)
编辑 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,
]
}
希望你能更好地理解我的目标...
这是标准的“协议不符合协议”问题。 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)