Следующий код скомпилирован без проблем

protocol Animal {
}

var animals = [Animal]()

Однако у нас есть новое требование, где нам нужно сравнить массив Animal

protocol Animal {
}

func x(a0: [Animal], a1: [Animal]) -> Bool {
    return a0 == a1
}

var animals = [Animal]()

Приведенный выше код приведет к ошибке компиляции

протокол «Животное» как тип не может соответствовать «Приравненному»


Мы склонны исправлять

protocol Animal: Equatable {
}

func x(a0: [Animal], a1: [Animal]) -> Bool {
    return a0 == a1
}

var animals = [Animal]()

В строке объявления массива мы получаем ошибку

протокол «Животное» может использоваться только в качестве общего ограничения, поскольку он имеет требования к типу Self или связанному с ним типу.

Могу я знать,

  1. Почему у нас может быть массив протоколов до того, как протокол будет соответствовать Equatable?
  2. Почему нам не разрешено иметь массив протоколов, если протокол соответствует Equatable?
  3. Какие есть хорошие способы исправить такую ​​ошибку?
0
Cheok Yan Cheng 14 Янв 2022 в 21:19
1
Это не может работать. Невозможно сравнивать протоколы, потому что практически любой тип может принять протокол. Представьте, что у вас есть тип Cat : Animal и Dog: Animal. Как вы сравниваете типы?
 – 
vadian
14 Янв 2022 в 21:37
2
Массив протокольного типа всегда — плохая идея, независимо от того, является ли он общим протоколом или нет. См. stackoverflow.com/questions/33112559/… Если бы вам действительно пришлось это сделать, вам нужно было бы использовать стирание типа (т.е. вам нужен был бы эквивалентный тип AnyAnimal). У Apple есть видео WWDC об этом: developer.apple.com/videos/play/wwdc2015. /408
 – 
matt
14 Янв 2022 в 22:00
Я все еще не понимаю. Прежде чем соответствовать Equatable, мне разрешено иметь массив протоколов. Но почему после того, как я соглашусь с Equatable, мне это запрещено? Почему вы думаете, что массив типа протокола - плохая идея? Я думал, что большинство основных языков потока, таких как Java, разрешают и приветствуют массив интерфейса (протокола).
 – 
Cheok Yan Cheng
14 Янв 2022 в 22:09
Вам разрешено создавать массив протоколов, но это не делает его хорошей идеей.
 – 
matt
14 Янв 2022 в 22:26
2
Наш собственный Роб Нейпир сделал отличный доклад, который касается именно этого, в частности, Equatable: youtu.be/_m6DxTEisR8?t =2538
 – 
Alexander
14 Янв 2022 в 22:35

3 ответа

Лучший ответ

Эта часть Swift может немного сбивать с толку, и есть планирует его улучшить.

Когда вы пишете что-то вроде a0: [Animal], вы говорите, что ваша функция принимает аргумент массива, элементы которого являются экзистенциалами протокола (протокола Animal).

Экзистенциальный Animal — это объект, предоставляющий своему пользователю унифицированный доступ ко всем требованиям (свойствам, методам, индексам, инициализаторам и т. д.) протокола Animal, независимо от конкретного типа базового объект (Cat, Dog и т. д.).

В посте нового мира SE-0335 ваш код должен быть написан так:

func x(a0: [any Animal], a1: [any Animal]) -> Bool {
    return a0 == a1
}

Проблема становится более ясной: нет никакой гарантии, что a0 и a1 содержат животных одного и того же типа. Теперь это буквально прописано в коде: это массивы любого типа животных. Каждый из них может содержать животных любого типа, и нет никакой связи между типами животных в a0 и в a1. Это проблема, потому что Equatable специфичен для проверки своих требований: его оператор == применим только к двум объектам одного типа.

Чтобы исправить это, вам нужно сделать вашу функцию универсальной, чтобы содержать a0 и a1 объекты определенного типа:

func x<A: Animal>(a0: [A], a1: [A]) -> Bool {
    return a0 == a1
}
1
Alexander 14 Янв 2022 в 22:59
Вы имели в виду a1: [A]
 – 
Leo Dabus
14 Янв 2022 в 22:49

Протокол не может соответствовать Equatable. Причина в том, что для этого требуется Я. Self относится к конкретному типу (например, struct/class), который соответствует Equatable. Если вы хотите иметь возможность использовать протокол вместо конкретного типа для массива, вам нужно написать функцию сжатия самостоятельно:

protocol Animal {
    
    var name: String { get }
    
}

func compare(lhsAnimals: [Animal], rhsAnimals: [Animal]) -> Bool {
    guard lhsAnimals.count == rhsAnimals.count else { return false}
    for i in 0..<lhsAnimals.count {
        if lhsAnimals[i].name != rhsAnimals[i].name {
            return false
        }
    }
    return true
}
0
Bulat Yakupov 14 Янв 2022 в 22:23
Этот код не компилируется
 – 
jnpdx
14 Янв 2022 в 21:53
Извините, быстро написал ответ без проверки, сейчас поправлю.
 – 
Bulat Yakupov
14 Янв 2022 в 21:58
Это неправильно, вы определенно можете иметь массив животных. let animals: [Animal] = [Dog(name: "Albert"), Dog(name: "Kiki"), Dog(name: "Caesar")]
 – 
Leo Dabus
14 Янв 2022 в 21:59
Хорошо, кажется, что лучше переписать мой ответ.
 – 
Bulat Yakupov
14 Янв 2022 в 22:15
1
Извините, ребята, я немного нервничаю, мой второй ответ здесь, так что могут быть некоторые ошибки :)
 – 
Bulat Yakupov
14 Янв 2022 в 22:29