Подтвердить что ты не робот

Дженерики не компилируются

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

class Translator<T:Hashable> {...}

class FooTranslator<String>:Translator<String> {...}

Идея состоит в том, что Translator использует T в качестве типа ключа в словаре. Это может быть, например, строка или перечисление. Подкласс предоставляет конкретный словарь.

Но происходит сбой, потому что: "Тип" String "не соответствует протоколу" Hashable ""

Но String соответствует Hashable. Он также не работает с Int, который также соответствует Hashable.

Если я удаляю ограничение типа, просто для тестирования (где я также должен отключить словарь, так как я не могу использовать ничего, что не может быть хешируемым в качестве ключа) - он компилируется

class Translator<T> {...}

class FooTranslator<String>:Translator<String> {...}

Что я делаю неправильно?

4b9b3361

Ответ 1

Я не разработчик Swift, но, увидев подобные проблемы в Java, я подозреваю, что проблема заключается в том, что на данный момент вы объявляете параметр типа String, потому что вы объявляете class FooTranslator<String> - поэтому type в Translator<String> - это просто параметр типа, который не имеет ограничений. Вы не хотите, чтобы параметр типа вообще был, я подозреваю (т.е. Вы не хотите, чтобы ваш FooTranslator был общим классом.)

Как отмечено в комментариях, в подклассах Swift родового класса также должны быть общие. Вы могли бы объявить параметр типа выброса, например:

class FooTranslator<T>:Translator<String>

который все еще избегает объявления нового параметра типа String, что и вызывало проблему. Это означает, что вы вводите новый параметр типа, когда вам не нужны какие-либо параметры типа, но возможно лучше, чем ничего...

Это все, исходя из предположения, что вам действительно нужен подкласс, например. для добавления или переопределения членов. С другой стороны, если вы просто хотите, чтобы тип был точно таким же, как Translator<String>, вместо него следует использовать псевдоним типа:

typealias FooTranslator = Translator<String>

Или даже смешайте их ужасно, если вы действительно хотите подкласс, но не хотите ссылаться на него общим способом:

class GenericFooTranslator<T>:Translator<String>
typealias FooTranslator = GenericFooTranslator<Int>

(Обратите внимание, что Int здесь намеренно не String, чтобы показать, что T in Translator не совпадает с T в FooTranslator.)

Ответ 2

Для начала объясните ошибку:

Дано:

class Foo<T:Hashable> { }

class SubFoo<String> : Foo<String> { }

Сложная часть здесь заключается в том, что мы ожидаем, что "String" означает строгую структуру Swift, которая содержит набор символов. Но это не так.

Здесь "String" - это имя общего типа, которым мы присвоили наш новый подкласс SubFoo. Это становится очень очевидным, если мы вносим некоторые изменения:

class SubFoo<String> : Foo<T> { }

Эта строка генерирует ошибку для T как использование необъявленного типа.

Тогда, если мы изменим строку на это:

class SubFoo<T> : Foo<T> { }

Мы вернулись к той же ошибке, которую вы изначально имели, "T" не соответствует "Hashable". Это очевидно здесь, потому что T не смущает имя существующего типа Swift, который соответствует "Hashable". Очевидно, что "T" является общим.

Когда мы пишем "String" , это также просто имя заполнителя для общего типа, а не тип String, который существует в Swift.


Если нам нужно другое имя для определенного типа общего класса, подходящий подход почти наверняка будет typealias:

class Foo<T:Hashable> {

}

typealias StringFoo = Foo<String>

Это абсолютно корректный Swift, и он просто компилируется.


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

Возвращаясь к исходной проблеме, сначала избавьтесь от ошибки:

class Foo<T: Hashable>

class SubFoo<T: Hashable> : Foo<T> { }

Это совершенно верно Swift. Но это может быть не особенно полезно для того, что мы делаем.

Единственная причина, по которой мы не можем сделать следующее:

class SubFoo<T: String> : Foo<T> { }

просто потому, что String не является классом Swift - это структура. И это не допускается для какой-либо структуры.


Если мы напишем новый протокол, который наследует от Hashable, мы можем использовать это:

protocol MyProtocol : Hashable { }

class Foo<T: Hashable> { }
class SubFoo<T: MyProtocol> : Foo<T> { }

Это совершенно верно.

Также обратите внимание, что нам действительно не нужно наследовать от Hashable:

protocol MyProtocol { }
class Foo<T: Hashable> { }
class SubFoo<T: Hashable, MyProtocol> { }

Это также отлично.

Однако обратите внимание, что по какой-либо причине Swift не позволит вам использовать класс здесь. Например:

class MyClass : Hashable { }

class Foo<T: Hashable> { }
class SubFoo<T: MyClass> : Foo<T> { }

Swift загадочно жалуется, что "T" не соответствует "Hashable" (даже когда мы добавляем необходимый код, чтобы сделать это так.


В конце концов, правильный подход и самый подход, подходящий для Swift, будет писать новый протокол, который наследуется от "Hashable" и добавляет любую функциональность, которая вам нужна.

Не должно быть строго важно, чтобы наш подкласс принял String. Важно, что независимо от того, что наш подкласс занимает, он имеет необходимые методы и свойства, которые нам нужны во всем, что мы делаем.