Быстрая попытка стирания типа: «Ссылка на недопустимый связанный тип» - PullRequest
1 голос
/ 26 сентября 2019

Я выполняю сфабрикованное упражнение, чтобы попытаться реализовать контейнер со стертым типом.

import Foundation

protocol MoverType {
    func move()
}
extension MoverType {
    func move() { print("\(type(of: self)) is moving") }
}

class Slithering: MoverType {}
class Walking: MoverType {}
class Trotting: MoverType {}

protocol Animal {
    associatedtype Mover: MoverType
    var mover: Mover { get }
}

class Snake: Animal {
    let mover = Slithering()
}
class Dog: Animal {
    let mover = Trotting()
}
class Human: Animal {
    let mover = Walking()
}

class AnyAnimal: Animal {  // ERROR: Type 'AnyAnimal' does not conform to protocol 'Animal'
    var _mover: () -> Mover
    init<A: Animal>(animal: A) where Mover == A.Mover {
        _mover = {
            return animal.mover
        }
    }

    // ERROR HERE: "Reference to invalid associated type "Mover" of of type "AnyAnimal"
    var mover: Mover { fatalError() }
}

let d = AnyAnimal(animal: Dog())
let s = AnyAnimal(animal: Snake())

let animals = [d, s]   // Array<AnyAnimal>
for a in animals {
    a.mover.move()
}

Я сознательно не хотел, чтобы мой контейнер AnyAnimal был контейнером AnyAnimal<T>.Потому что я хотел бы иметь возможность хранить множество Animal экземпляров в Array<AnyAnimal>.

Однако, как видно из приведенного выше кода, компилятор жалуется на класс AnyAnimal.Насколько я понимаю, требование протокола Mover будет разрешено общим предложением where в инициализаторе AnyAnimal.

Пожалуйста, помогите мне понять, чего не хватает.(Или вообще возможно создать не универсальную оболочку для стирания типов?)

Ответы [ 2 ]

2 голосов
/ 27 сентября 2019

Ваш код не компилируется, потому что связанный тип должен быть разрешен во время компиляции, предоставляя конкретную реализацию для протокола Mover.

Что вы можете сделать, так это стереть MoverType протокол:

struct AnyMover: MoverType {
    private let mover: MoverType

    init(_ mover: MoverType) {
        self.mover = mover
    }

    func move() {
        mover.move()
    }
}

class AnyAnimal: Animal {
    let mover: AnyMover

    init<A: Animal>(animal: A) {
        mover = AnyMover(animal.mover)
    }    
}
1 голос
/ 27 сентября 2019

Я согласен с Кристиком по поводу добавления ластика типа AnyMover.Это способ сделать буквально то, что вы говорите.Но если вы идете по этому пути, вы, как правило, неправильно разработали свои протоколы.Например, хотя я знаю, что это сфабриковано, это отличный пример неправильно разработанного протокола.Протокол Животного почти наверняка должен быть:

protocol Animal {
    func move()
}

А затем ваши associatedtype и связанные с ними проблемы испаряются.И ваш интерфейс имеет больше смысла.Животное может двигаться.Это не что-то "с двигателем".Я ожидаю, что каждое использование этого типа будет выглядеть почти идентично вашему примеру: animal.mover.move().Это говорит о том, что mover - это деталь реализации, которая не должна волновать звонящих.

Что еще может сделать в цикле над [Animal]?Как вы могли бы написать общий код, который использовал бы .mover и не вызов .move?Других методов не существует.

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

Кстати, еще один подход - сохранить Movers, но просто позволить им быть протоколом.Звонящий действительно хочет знать разницу между сканерами и слайтерами?Разве это не скрывает цели?Если это так, вы можете пойти по этому пути:

protocol Animal {
    var mover: MoverType { get }
}

И снова все проблемы испаряются.

В любом случае, вы все равно можете автоматически реализовать свой метод move(), если есть mover доступный.Например, вы можете разработать протоколы следующим образом:

// Something that moves animals
protocol Mover {
    func move(animal: Animal)
}

// Something that has a mover
protocol MoverProviding {
    var mover: Mover { get }
}

// And of course Animals. They might be MoverProviding. They might not.
protocol Animal {
    func move()
}

// But if they *are* MoverProviding, we can use that.
extension Animal where Self: MoverProviding {
    func move() {
        mover.move(animal: self)
    }
}

Когда вы набираете associatedtype, вы должны думать: «Этот протокол предназначен для добавления дополнительных алгоритмов (методов и функций) к другим типам."Если цель протокола - разрешить разнородные коллекции, вы, вероятно, на неправильном пути.Стираторы типов иногда полезны и важны, но если вы чувствуете, что они вам очень нужны (и особенно , если они вам нужны из-за разнородных коллекций), у вас, вероятно, есть проблема с дизайном.

...