Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift protocol with constrained associated type error "Type is not convertible"

I have created 2 protocols with associated types. A type conforming to Reader should be able to produce an instance of a type conforming to Value.

The layer of complexity comes from a type conforming to Manager should be able to produce a concrete Reader instance which produces a specific type of Value (either Value1 or Value2).

With my concrete implementation of Manager1 I'd like it to always produce Reader1 which in turn produces instances of Value1.

Could someone explain why

"Reader1 is not convertible to ManagedReaderType?"

When the erroneous line is changed to (for now) return nil it all compiles just fine but now I can't instantiate either Reader1 or Reader2.

The following can be pasted into a Playground to see the error:

import Foundation

protocol Value {
    var value: Int { get }
}

protocol Reader {
    typealias ReaderValueType: Value
    func value() -> ReaderValueType
}

protocol Manager {
    typealias ManagerValueType: Value

    func read<ManagerReaderType: Reader where ManagerReaderType.ReaderValueType == ManagerValueType>() -> ManagerReaderType?
}

struct Value1: Value {
    let value: Int = 1
}

struct Value2: Value {
    let value: Int = 2
}

struct Reader1: Reader {
    func value() -> Value1 {
        return Value1()
    }
}

struct Reader2: Reader {
    func value() -> Value2 {
        return Value2()
    }
}

class Manager1: Manager {
    typealias ManagerValueType = Value1

    let v = ManagerValueType()
    func read<ManagerReaderType: Reader where ManagerReaderType.ReaderValueType == ManagerValueType>() -> ManagerReaderType? {
        return Reader1()// Error: "Reader1 is not convertible to ManagedReaderType?" Try swapping to return nil which does compile.
    }
}

let manager = Manager1()
let v = manager.v.value
let a: Reader1? = manager.read()
a.dynamicType
like image 274
Jay Avatar asked Aug 01 '15 21:08

Jay


1 Answers

The error occurs because ManagerReaderType in the read function is only a generic placeholder for any type which conforms to Reader and its ReaderValueType is equal to the one of ManagerReaderType. So the actual type of ManagerReaderType is not determined by the function itself, instead the type of the variable which gets assigned declares the type:

let manager = Manager1()
let reader1: Reader1? = manager.read() // ManagerReaderType is of type Reader1
let reader2: Reader2? = manager.read() // ManagerReaderType is of type Reader2

if you return nil it can be converted to any optional type so it always works.

As an alternative you can return a specific type of type Reader:

protocol Manager {
    // this is similar to the Generator of a SequenceType which has the Element type
    // but it constraints the ManagerReaderType to one specific Reader
    typealias ManagerReaderType: Reader

    func read() -> ManagerReaderType?
}

class Manager1: Manager {

    func read() -> Reader1? {
        return Reader1()
    }
}

This is the best approach with protocols due to the lack of "true" generics (the following isn't supported (yet)):

// this would perfectly match your requirements
protocol Reader<T: Value> {
    fun value() -> T
}

protocol Manager<T: Value> {
    func read() -> Reader<T>?
}

class Manager1: Manager<Value1> {
    func read() -> Reader<Value1>? {
        return Reader1()
    }
}

So the best workaround would be to make Reader a generic class and Reader1 and Reader2 subclass a specific generic type of it:

class Reader<T: Value> {
    func value() -> T {
        // or provide a dummy value
        fatalError("implement me")
    }
}

// a small change in the function signature
protocol Manager {
    typealias ManagerValueType: Value
    func read() -> Reader<ManagerValueType>?
}

class Reader1: Reader<Value1> {
    override func value() -> Value1 {
        return Value1()
    }
}

class Reader2: Reader<Value2> {
    override func value() -> Value2 {
        return Value2()
    }
}

class Manager1: Manager {
    typealias ManagerValueType = Value1

    func read() -> Reader<ManagerValueType>? {
        return Reader1()
    }
}

let manager = Manager1()

// you have to cast it, otherwise it is of type Reader<Value1>
let a: Reader1? = manager.read() as! Reader1?

This implementation should solve you problem, but the Readers are now reference types and a copy function should be considered.

like image 66
Qbyte Avatar answered Oct 05 '22 13:10

Qbyte