Почему я не могу назначить этот класс, соответствующий протоколу, переменной типа протокола? - PullRequest
0 голосов
/ 16 июня 2019

У меня есть игрушечный пример, который я не могу найти решения для онлайн.

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>()
        }
    }
}

Я не уверен, в чем здесь проблема или что я должен искать в порядкечтобы понять это.Я все еще думаю о протоколах, как об интерфейсах на другом языке, и я рассматриваю BinaryTree как допустимую реализацию Tree, и единственным ограничением для F является то, что это должен быть Tree.Чтобы запутать вещи еще больше, я не уверен, почему следующий фрагмент компилируется, учитывая, что вышеупомянутый не делает.

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

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

Любые указатели или объяснения будут с благодарностью.

Ответы [ 5 ]

0 голосов
/ 16 июня 2019

Ваш подход к этому неверен.В исходном примере вам нужно будет указать тип элемента (E) и контейнера (BinaryTree или RedBlackTree) в дополнение к значению enum.Это не имеет смысла.

Вместо этого вы должны сконструировать менеджер, чтобы принимать дерево в качестве аргумента конструктора, что позволяет Swift выводить общие аргументы, т.е.

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>())

В качестве альтернативы вы должны посмотретьв использовании непрозрачных типов возврата в Swift 5.1 в зависимости от конечной цели (пример здесь явно не сценарий реального мира)

0 голосов
/ 16 июня 2019

Нечто подобное кажется разумным.Смысл был в том, чтобы попытаться получить некоторую часть логики, которая определяла бы, когда TreeManager использует один тип Tree против другого.

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()
    }

Это вводит другой протокол, который заботится о выборе реализации дерева ипредложение where указывает, что средство выбора возвращает дерево типа T.

. Я думаю, что все это было результатом невозможности объявить tree типа Tree<T>.Это немного более многословно, но в основном то, что должно было бы выглядеть с универсальным интерфейсом.Я думаю, что я тоже задал вопрос плохо.Я должен был просто опубликовать версию, которая не компилировалась, и попросить найти решение, вместо того, чтобы идти на шаг дальше и сбивать всех с толку.

0 голосов
/ 16 июня 2019
protocol Foo {
    associatedtype F
}

class FooClass : Foo {
    typealias F = String
}

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

    init(){
        foo = FooClass() as! M
    }
}
0 голосов
/ 16 июня 2019

Я не знаю, что вы пытаетесь сделать, но, конечно, это невозможно. из вашего примера вы пытаетесь создать Bar класс с generic. и что это не подходящий способ создания объекта generic, потому что создание объекта generic состоит в том, чтобы заставить объект принимать любой тип.

Вот краткое объяснение generic из Википедии .

В первом абзаце, который гласит: " Универсальное программирование - это стиль компьютерного программирования, в котором алгоритмы пишутся в терминах типов , которые будут определены позже", , которые затем создаются при необходимости для определенных типов, предоставляемых в качестве параметров . "

очень ясно, в чем смысл to-be-specified-later, верно :)

Вернуться к вашему примеру:

class Bar<F: Foo> {

    let foo: F

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

Из приведенного выше кода это параметр типа F, который имеет ограничение на тип Foo. и вы пытаетесь создать экземпляр переменной foo с конкретной реализацией, то есть FooClass. и это невозможно, поскольку переменная foo имеет тип F (абстрактный). конечно, мы можем уменьшить его, например, foo = FooClass() as! F, но тогда foo ограничивается только FooClass, так зачем тогда вообще беспокоиться об общем?

Надеюсь, это поможет :)

0 голосов
/ 16 июня 2019

Я не понимаю контекста ситуации, в которой это будет. Однако я предложил два решения:

1

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

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

2

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

    init(){
        foo = FooClass()
    }
}

То, что вы сейчас делаете, не имеет логического смысла для всего, что вы пытаетесь достичь

...