Use Type Erasure return Generi c Введите функцию с помощью Swift (невозможно преобразовать возвращаемое выражение типа…) - PullRequest
1 голос
/ 16 июня 2020

У меня проблема с дженериками в swift. Давайте раскроем мой код.


protocol FooProtocol {
    associatedtype T
}

protocol Fooable { }
extension Int : Fooable { }
extension String: Fooable { }

class AnyFoo<T>: FooProtocol {
    init<P: FooProtocol>(p: P) where P.T == T { }
}

class FooIntImpClass: FooProtocol {
    typealias T = Int
}

class FooStringImpClass: FooProtocol {
    typealias T = String
}

func createOne(isInt: Bool) -> AnyFoo<Fooable> {
    if isInt {
        let anyFoo = AnyFoo(p: FooIntImpClass())
          return anyFoo
    } else {
        let anyFoo = AnyFoo(p: FooStringImpClass())
        return anyFoo
    }
}

func createTwo<F: Fooable>(isInt: Bool) -> AnyFoo<F> {
    if isInt {
        let anyFoo = AnyFoo(p: FooIntImpClass())
          return anyFoo
    } else {
        let anyFoo = AnyFoo(p: FooStringImpClass())
        return anyFoo
    }
}

createOne получена ошибка

Невозможно преобразовать возвращаемое выражение типа AnyFoo (также известного как AnyFoo) в возвращаемый тип AnyFoo

createTwo произошла ошибка

Невозможно преобразовать возвращаемое выражение типа AnyFoo (также известного как AnyFoo) в возвращаемый тип AnyFoo

Почему так происходит. Возвращаю правильное значение.

А в чем разница с createOne и createTwo

Ответы [ 3 ]

1 голос
/ 16 июня 2020

Вы пытались достичь следующего? (скомпилировано и протестировано с Xcode 11.4)

func createOne() -> some FooProtocol {
    let anyFoo = AnyFoo(p: FooImpClass())
    return anyFoo
}
1 голос
/ 16 июня 2020

ИЗМЕНИТЬ, чтобы ответить на изменение вопроса:

createTwo не работает, потому что у вас такое же заблуждение, как я сказал в своем исходном ответе. createTwo самостоятельно решил, что F должен быть String или Int, а не «любым типом, который соответствует Fooable».

Для createOne у вас есть другой общий заблуждение. Generi c классы инвариантны . AnyFoo<String> не является разновидностью AnyFoo<Fooable>. На самом деле это совершенно не связанные типы! Подробнее см. здесь .

По сути, то, что вы пытаетесь сделать, нарушает безопасность типов, и вы переделываете свои API и выбираете другой подход.


Исходный ответ (для первоначального пересмотра вопроса)

Похоже, у вас распространенное неправильное представление о дженериках. Параметры Generi c определяются вызывающим, а не вызываемым.

В createOne вы возвращаете anyFoo, который имеет тип AnyFoo<Int>, а не AnyFoo<P>. Метод (вызываемый) решил, что P должно быть Int. Этого не должно происходить, потому что вызывающий решает, какими должны быть общие c параметры. Если вызываемый - generi c, он должен иметь возможность работать с любым типом (в пределах ограничений). В любом случае, P не может быть здесь Int, поскольку P: FooProtocol.

Ваш метод createOne вообще не должен быть общим c, так как он работает только с Int:

func createOne() -> AnyFoo<Int> {
    let anyFoo = AnyFoo(p: FooImpClass())
    return anyFoo
}
0 голосов
/ 16 июня 2020

ИЗМЕНИТЬ Наконец-то мне удалось сохранить ваш where clause:)

ИЗМЕНИТЬ Все еще не уверен, что вы хотите сделать, и я все еще согласен с @Sweeper но я люблю сильно злоупотреблять дженериками :):


protocol FooProtocol {
    associatedtype T
    init()
}

protocol Fooable { }
extension Int : Fooable { }
extension String: Fooable { }

class AnyFoo<T>: FooProtocol {
    init<P: FooProtocol>(p: P) where P.T == T { }
    init<T>(p: T.Type) { }
    required init() { }
}

class FooIntImpClass: FooProtocol {
    typealias T = Int
    required init() { }
}

class FooStringImpClass: FooProtocol {
    typealias T = String
    required init() { }
}


func createOne<F: FooProtocol>(foo: F.Type) -> AnyFoo<F.T> {
    let anyFoo = AnyFoo<F.T>(p: F.init())
        return anyFoo
}

func createTwo<F: FooProtocol>(foo: F.Type) -> some FooProtocol {
    let anyFoo = AnyFoo<F.T>(pk: F.T.self)
    return anyFoo
}

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

Edit

да, я правда не знаю:

let one = createOne(foo: FooStringImpClass.self) // AnyFoo<String>
print(type(of: one).T) // "String\n"
let two = createTwo(foo: FooIntImpClass.self) // AnyFoo<Int>
print(type(of: two).T) // "Int\n"

Это то, что вы хотели?

Я не уверен, что вы хотите с этим делать, но предлагаю вам попробовать поместить предложение where в класс AnyFoo вместо его инициализатора. И я должен добавить, что ваше предложение where в инициализаторе было неправильным, например, Sweeper сказал:

В любом случае, P все равно не может быть Int здесь, поскольку P: FooProtocol.

Компилируется следующий код:

protocol FooProtocol {
    associatedtype T
}

class AnyFoo<T>: FooProtocol where T: FooProtocol {
    init<P: FooProtocol>(p: P) { }
}

class FooImpClass: FooProtocol {
    typealias T = Int
}

func createOne<P: FooProtocol>() -> AnyFoo<P> {
    let anyFoo: AnyFoo<P> = AnyFoo(p: FooImpClass())
    return anyFoo
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...