2015-01-13 23 views
27

Próbuję zaimplementować następującą strukturę przy użyciu generycznych. Uzyskanie dziwnego błędu kompilatora, nie można zrozumieć, dlaczego.Dziwny błąd generyczny

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

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

Chodzi o to, że Translator używa T jako typu klucza w słowniku. Może to być np. Ciąg lub wyliczenie. Podklasa zapewnia konkretny słownik.

Ale to nie z tego powodu: "Typ 'String' nie jest zgodny z protokołem 'Hashable'"

Ale String zgodny Hashable! Czy ja jestem szalony? Nie działa również z Int, który również jest zgodny z Hashable. Nie działa również, jeśli zmienię Hashable na Equatable, który powinien być również zaimplementowany przez oba.

Jeśli usunąć ograniczenie typu, tylko do testowania (gdzie mam również wyłączyć słownik, bo nie można używać niczego nie hashable jako klucz tam) - kompiluje

class Translator<T> {...} 

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

Co mam robić źle?

Odpowiedz

17

Nie jestem Swift deweloper, ale widząc podobne problemy w Javie, podejrzewam, że problem jest, że w tej chwili jesteś deklarując parametr typu o nazwie String bo jesteś deklarowania class FooTranslator<String> - tak ten typ argumentu w Translator<String> to tylko ten typ parametru, który nie ma ograniczeń. Nie chcą parametr typu w ogóle, podejrzewam (to ty nie chcesz FooTranslator być sama. Klasa ogólne)

Jak zauważył w komentarzach, w Swift subclasses of a generic class also have to be generic. Można ewentualnie zadeklarować typ parametru jednorazowe, jak to:

class FooTranslator<T>:Translator<String> 

który nadal unika uznającą nowy parametr typu o nazwie String, co było, co było przyczyną problemu. Oznacza to, że wprowadzasz nowy parametr, jeśli nie chcesz mieć parametrów dowolnego typu, ale prawdopodobnie jest to lepsze niż nic ...

Wszystko opiera się na założeniu, że naprawdę potrzebujesz podklasy, np. dodawać lub zastępować członków. Z drugiej strony, jeśli chcesz po prostu typ, który jest dokładnie taka sama jak Translator<String>, należy użyć typu alias Zamiast:

typealias FooTranslator = Translator<String> 

lub nawet mieszać dwóch w okropny sposób, jeśli rzeczywiście chcą podklasą ale nie chcą mieć do odnoszą się do niego w sposób ogólny:

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

(Zauważ, że Int tutaj jest celowo nie String, aby pokazać, że T w Translator nie jest taka sama jak T w FooTranslator.)

+0

To nie to. Istnieje kwestia/ograniczenie dotyczące Swift, zobacz http://stackoverflow.com/questions/24138359/limitation-with-classes-derived-from-generic-classes-in-swift – Ixx

+0

@lxx: Ick. Podejrzewam, że to nadal jest * przyczyna *, ale chcesz innego rozwiązania. Edytowałem swoją odpowiedź z możliwym rozwiązaniem, aby spróbować. –

+0

Cóż, tak, rozwiązanie z parametrem throw away działa, ale nie próbowałem go, ponieważ jest brzydki. Z drugiej strony, próbowałem ponownie rozwiązania typalias i działa teraz. Wystąpił problem, ponieważ (wybrana odpowiedź) używa Int jako symbolu zastępczego, dlatego próbowałem używać parametru String, który powodował błędy. Zamieniłem to na T i zadeklarowałem typy poniżej klasy, a nie kompilacje. Wciąż jest to obejście problemu. I tak dziękuję za skierowanie mnie z powrotem do właściwego rozwiązania :) – Ixx

96

Na początek wyjaśnijmy błąd:

Dane:

class Foo<T:Hashable> { } 

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

Mylące częścią jest to, że możemy spodziewać się „string” oznacza Swift określoną strukturę, która przechowuje zbiór znaków. Ale nie jest.

W tym miejscu "String" jest nazwą ogólnego typu, który nadaliśmy naszej nowej podklasie, SubFoo. Staje się to oczywiste, gdy wykonujemy pewne zmiany:

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

Linia ta generuje błąd dla T jak stosowanie typu nierejestrowanej.

Następnie gdybyśmy zmienić linię do tego:

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

Wracamy do tego samego błędu pierwotnie miał, „T” nie odpowiada „Hashable”. Jest to oczywiste, ponieważ T nie jest myląco nazwą istniejącego typu Swift, który jest zgodny z "Hashable". To oczywiste, że "T" to rodzajowy.

Kiedy piszemy "String", jest to po prostu nazwa zastępcza dla typu ogólnego, a nie faktycznie typu String, który istnieje w Swift.


Jeśli chcemy inną nazwę dla określonego typu rodzajowego klasy, odpowiednie podejście jest prawie na pewno typealias:

class Foo<T:Hashable> { 

} 

typealias StringFoo = Foo<String> 

To jest całkowicie poprawny Swift i kompiluje dobrze.


Jeżeli zamiast tego, co chcemy jest rzeczywiście podklasy i dodać metody lub właściwości do klasy ogólnej, to co potrzebujemy to klasa lub protokół, który sprawi, że nasz generic dokładniej do czego potrzebujemy.

Wracając do pierwotnego problemu, niech najpierw pozbyć się błędu:

class Foo<T: Hashable> 

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

To jest całkowicie poprawny Swift. Ale może nie być szczególnie przydatne dla tego, co robimy.

Jedynym powodem, nie możemy wykonać następujące czynności:

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

jest po prostu dlatego, że nie jest String Swift klasa - to struktura. A to nie jest dozwolone dla żadnej struktury.


Jeśli piszemy nowy protokół, który dziedziczy Hashable, możemy użyć, że:

protocol MyProtocol : Hashable { } 

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

To jest całkowicie poprawny.

Należy również pamiętać, że w rzeczywistości nie muszą dziedziczyć Hashable:

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

Jest również całkowicie poprawny.

Należy jednak pamiętać, że z dowolnego powodu Swift nie zezwoli na korzystanie z zajęć tutaj. Na przykład:

class MyClass : Hashable { } 

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

Swift tajemniczo skarży się, że „T” nie odpowiada „Hashable” (nawet gdy dodamy kod niezbędny, aby tak było


w końcu prawo. a podejście najbardziej odpowiednie dla Swift ma polegać na pisaniu nowego protokołu, który dziedziczy po "Hashable" i dodaje do niego dowolną funkcjonalność.

Nie powinno być absolutnie ważne, aby nasza podklasa akceptowała numer String. Powinno być ważne, że wha Podejmij naszą podklasę, ma niezbędne metody i właściwości, których potrzebujemy do tego, co robimy.