Why can't I assign this protocol conforming class to a variable of the protocol type?

China☆狼群 提交于 2019-12-25 01:15:34

问题


I've got a toy example here that I can't find any solutions for online.

protocol Tree {
    associatedtype Element

    // some nice tree functions
}

class BinaryTree<T> : Tree {
    typealias Element = T
}

class RedBlackTree<T> : Tree {
    typealias Element = T
}


enum TreeType {
    case binaryTree
    case redBlackTree
}

// Use a generic `F` because Tree has an associated type and it can no
// longer be referenced directly as a type. This is probably the source
// of the confusion.
class TreeManager<E, F:Tree> where F.Element == E {
    let tree: F

    init(use type: TreeType){
        // Error, cannot assign value of type 'BinaryTree<E>' to type 'F'
        switch(type){
        case .binaryTree:
            tree = BinaryTree<E>()
        case .redBlackTree:
            tree = RedBlackTree<E>()
        }
    }
}

I'm not sure what the problem here is or what I should be searching for in order to figure it out. I'm still thinking of protocols as I would interfaces in another language, and I view a BinaryTree as a valid implementation of a Tree, and the only constraint on F is that it must be a Tree. To confuse things even more, I'm not sure why the following snippet compiles given that the one above does not.

func weird<F:Tree>(_ f: F){ }

func test(){
    // No error, BinaryTree can be assigned to an F:Tree
    weird(BinaryTree<String>())
}

Any pointers or explanations would be greatly appreciated.


回答1:


I don't understand the context of the situation this would be in. However, I have provided two solutions though:

1:

class Bar<F:Foo> {
    let foo: FooClass.F

    init(){
        foo = FooClass.F()
    }
}

2:

class Bar<F:Foo> {
    let foo: FooClass

    init(){
        foo = FooClass()
    }
}

What you are currently doing doesn't make logical sense, to whatever you are trying to achieve




回答2:


I don't know what are you trying for, but of course it's not possible to do that. from your example, you attempt to create Bar class with generic. and that not the appropriate way to create a generic object, because the creation of generic object is to make the object to accept with any type.

Here's some brief explanation of the generic taken from Wikipedia.

On the first paragraph that says, "Generic programming is a style of computer programming in which algorithms are written in terms of types to-be-specified-later that are then instantiated when needed for specific types provided as parameters."

it's very clear about what are the meaning of to-be-specified-later, right :)

Back to your example :

class Bar<F: Foo> {

    let foo: F

    init() {
        // Error, cannot assign value of type 'FooClass' to type 'F'
        foo = FooClass()
    }
}

From the above code, it is type parameter F which has a constraint to a type Foo. and, you try to create an instance for foo variable with a concrete implementation, that is FooClass. and that's not possible since the foo variable is a F type(which is abstract). of course we can downcast it, like this foo = FooClass() as! F, but then the foo is only limited to FooClass, so why even bother with generic then ?

Hope it help :)




回答3:


Your approach to this is wrong. In your original example, you would have to specify the type of both the element (E) and the container (BinaryTree or RedBlackTree), in addition to the enum value. That make no sense.

Instead, you should construct the manager to take a tree as the constructor argument, allowing Swift to infer the generic arguments, i.e.

class TreeManager<E, F: Tree> where F.Element == E {
    var tree: F

    init(with tree: F) {
        self.tree = tree
    }
}

let manager = TreeManager(with: BinaryTree<String>())

Alternatively, you should look into using opaque return types in Swift 5.1 depending on what the final goal is (the example here is obviously not a real world scenario)




回答4:


Something like this seems reasonable. The point was to try and have some piece of logic that would determine when the TreeManager uses one type of Tree vs another.

protocol TreePicker {
    associatedtype TreeType : Tree

    func createTree() -> TreeType
}

struct SomeUseCaseA<T>: TreePicker {
    typealias TreeType = RedBlackTree<T>

    func createTree() -> TreeType {
        return RedBlackTree<T>()
    }
}

struct SomeUseCaseB<T>: TreePicker {
    typealias TreeType = BinaryTree<T>

    func createTree() -> TreeType {
        return BinaryTree<T>()
    }
}

class TreeManager<T, Picker: TreePicker> where Picker.TreeType == T {
    let tree: T

    init(use picker: Picker){
        tree = picker.createTree()
    }

This introduces another protocol that cares about picking the tree implementation and the where clause specifies that the Picker will return a tree of type T.

I think all of this was just the result of not being able to declare the tree of type Tree<T>. Its a bit more verbose but its basically what it would have had to look like with a generic interface instead. I think I also asked the question poorly. I should have just posted the version that didn't compile and asked for a solution instead of going one step further and confusing everyone.




回答5:


protocol Foo {
    associatedtype F
}

class FooClass : Foo {
    typealias F = String
}

class Bar<M:Foo> {
    let foo: M

    init(){
        foo = FooClass() as! M
    }
}


来源:https://stackoverflow.com/questions/56615215/why-cant-i-assign-this-protocol-conforming-class-to-a-variable-of-the-protocol

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