Reputation: 7290
Here is my problem:
Let's say I have a protocol with associatedtype
referring to its metatype:
protocol TestMeta {
associatedtype T
var x : T.Type { get }
var y : T { get }
}
If I create a struct with a concrete type, no problem:
struct AMeta : TestMeta {
var x : Int.Type
var y : Int
}
But if the associatedtype
refers to a protocol, I've got the "Type 'BMeta' does not conform to protocol 'TestMeta'" error:
protocol P { }
struct BMeta : TestMeta {
var x : P.Type
var y : P
}
(even if I add the typealias
definition to help the inference engine)
Of couse, if I do not refer to the metatype, everything works with concrete types and protocols, even if I have other variables of the metatype not defined within the protocol:
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
}
So if anyone can explain what I do wrong? And how can I achieve my goal? Thanks in advance.
EDIT: But as @New Dev pointed out, I did not explain what I was looking for. I want to be able to do something like this:
struct S : P { }
let b = BMeta(x: S.self, y: S())
Knowing that's still compiling with the NoMeta protocol:
let bb = BNoMeta(z: S(), t: S.Type)
EDIT 2: In the end I wish to do something like this:
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()
EDIT 3: Ok, ok, here is my real use case, I thought it was better simplifying things, but it seems not...
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,
]
}
Hope you'll understand better my aim...
Upvotes: 0
Views: 96
Reputation: 299603
This is the standard "protocols do not conform to protocols" question. Contract is a protocol that requires conforming types to also conform to Initializable. It is not itself Initializable. In fact, this is a canonical example of why protocols can't conform to themselves in the most general case. If it did, I could write Contract.init()
and get back a...what? There's no way to allocate memory for "an abstract Contract."
Since Contract does not conform to Initializable, it can't be Option directly. Swift today also lacks any mechanism to talk about protocol inheritance. You cannot say that Option must be a protocol that requires Initializable. It's just beyond the Swift type system.
That said, this is a pretty inflexible approach. It requires that every type accept a trivial init
, which is very limiting to what kinds of types can conform. There are much better ways IMO to implement this kind of factory.
Get rid of Initializable, get rid of the static
requirement (you'll see why later), and replace the type with a builder function.
protocol OptionListFactory {
associatedtype Option
var availableOptions: [() -> Option] { get }
}
That causes the extension to be tweaked this way:
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
}
}
}
And a simple factory:
protocol Contract {}
struct Contract01 : Contract {}
struct Contract02 : Contract {}
struct ContractFactory : OptionListFactory {
let availableOptions: [() -> Contract] = [
Contract01.init, // init rather than self
Contract02.init,
]
}
But what if you have some type you want to vend that needs some extra information to construct? Doing it this way makes that straightforward.
// 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"
By making the factories instances rather than using static
, they are much more flexible and can be configured. And by using functions, you can compose in ways you may not have considered. For example, you could add a debug print statement every time an option is created:
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)
Upvotes: 2