2014-10-08 23 views
5

Zobacz przykład poniżej zawierający. Kompilator zgłasza błąd w ostatnim wierszu (oznaczonym jako COMPILE ERROR), w którym przypisuję instancję SimpleTrain do typu protokołu, który jest zgodny z moją najlepszą oceną. Jak mogę go skompilować? Co ja robię źle? Czy jest to problem z kompilatorem?Nie można przypisać instancji klasy do jej typu protokołu?

protocol Train { 
    typealias CarriageType 

    func addCarriage(carriage: CarriageType) 
    func shortTrain<ShortType: Train where ShortType.CarriageType == CarriageType>() -> ShortType 
} 

class SimpleTrain<T> : Train { 
    typealias CarriageType = T 
    private var carriages: [T] = [T]() 

    func addCarriage(carriage: T) { 
     carriages.append(carriage) 
    } 

    func shortTrain<ShortType: Train where ShortType.CarriageType == CarriageType>() -> ShortType { 
     let short = SimpleTrain<T>() 
     short.addCarriage(carriages[0]) 
     return short //COMPILE ERROR: SimpleTrain<T> is not convertible to 'ShortType' 
    } 
} 

EDIT: Nawet kiedy wyraźnie przybity przez shortTrain „s typ zwracany powyżej (tak, że ostatnia linia fragmencie kodu powyżej odczytuje return short as ShortType) jako suggested by Antonio wciąż jest kompilacja błąd podczas funkcję shortTrain wywołanie:

let s = SimpleTrain<String>() 
s.addCarriage("Carriage 1") 
s.addCarriage("Carriage 2") 

let a = s.shortTrain() //ERROR: Cannot convert the expression's type '()' to type 'Train' 
let b = s.shortTrain<SimpleTrain<String>>() //ERROR: cannot explicitly specialize a generic function 

Odpowiedz

3

Po pierwsze, chcesz przeczytać na ten temat canonical thread od devforums. Szczególnie chcesz pominąć czytanie komentarzy jckarter.

Teraz do edytowanej pytanie:

let a = s.shortTrain() //ERROR: Cannot convert the expression's type '()' to type 'Train' 

To dlatego, że nie dały kompilatora wystarczająco dużo informacji, aby określić typ a.Przemyśleć to, co widzi:

func shortTrain<ShortType: Train where ShortType.CarriageType == CarriageType>() -> ShortType { 
let a = s.shortTrain() 

Kompilator musi dowiedzieć się, rodzaj a w czasie kompilacji i nie może obsłużyć abstrakcyjne typy. Potrzebny jest w pełni określony typ dla ShortType (wszystko jest przybite, wszystkie określone typy genów, wszystkie aliasy typu zostały rozwiązane). Rozgląda się i widzi pewne ograniczenia na ShortType, ale nie widzi niczego, co faktycznie daje typ. Wszystko, co ma, to a, co nie daje żadnych wskazówek.

To niestety oznacza, że ​​musisz wyraźnie powiedzieć, co chcesz zrobić.

let a: SimpleTrain<String> = s.shortTrain() 

To chyba przeciwieństwo tego, do czego się wybierasz, ale chodzi tylko o to, co możesz teraz zrobić w Swift. Zespół Swift wskazał (wiele razy), że jest dobrze poinformowany o tych problemach z powiązanymi typami (i kilkoma innymi pokrewnymi słabościami w systemie typu). Są świadomi systemu typu Scala, który radzi sobie z tymi rzeczami i ma wiele wspólnego z bieżącym systemem typu Swift (choć z mojego doświadczenia wynika, że ​​uzyskanie złożonych, zależnych od ścieżki typów skojarzonych do pracy w Scali może również doprowadzić do rozdarcia włosów).

To powiedziawszy, nie jest dokładnie oczywiste z twojego przykładu, co zaplanowałeś zrobić z tą funkcjonalnością. Czy niektóre pociągi zwrócą inny typ niż ich shortTrain()?

Uważam, że problemy te często wysadzają się w ogólnym przypadku, ale w konkretnych przypadkach są raczej rozwiązywalne dla aplikacji znajdującej się przed tobą. Trudno jest stworzyć naprawdę arbitralne typy w Swift w kodzie, który może rozwiązać każdy problem, ale często działa, gdy koncentrujesz się na typach, których naprawdę potrzebujesz. Na przykład, jeśli shortTrain() zwrócił Self, to oczywiście staje się prostsze. Jeśli osoba dzwoniąca zna żądany wynikowy typ, prawdopodobnie prawdopodobnie poradzi sobie z tym init(shorten:). Metoda protokołu, taka jak shortCarriages() -> [CarriageType], może zapewnić dobry mostek. Zachowaj elastyczność w projektowaniu, a jedno z nich prawie na pewno zadziała.

+0

Dziękuję za tę odpowiedź Rob. Odpowiedział na wszystkie moje pytania (w tym niektóre, które mogłem zapytać gdzie indziej w przyszłości, na przykład o tym, co sądzą o tym członkowie zespołu Swift). Niestety, nie mogę podać żadnych bardziej szczegółowych informacji. Podałem sztuczny przykład, aby usunąć wszystkie informacje związane z biznesem. Jednak podany przykład jest tak bliski jak to tylko możliwe i stwierdzam, że produkt jest biblioteką i nie mogę wymagać, aby użytkownicy wyraźnie określili typ dla wielu wywołań metod. Znalazłem inny sposób. Wciąż byłyby to dobre protokoły z otwartymi aliasami typów do użycia jako typy – drasto

4

wyraźne przygnębiony z jednej zmiennej:

let short = SimpleTrain<T>() as ShortType 

lub wartość powrotu:

return short as ShortType 

rozwiązuje ten problem.

Aktualizacja

Kiedy odpowiadając na pytanie, zastanawiałem się, w jaki sposób protokół Train może być używany jako typ zwracany, jest to typealiased.

Spójrz na ten kod:

protocol ProtocolWithNoAlias {   
} 

protocol ProtocolWithAlias { 
    typealias AliasType 
} 

var x: ProtocolWithNoAlias? 
var y: ProtocolWithAlias? 

Ostatni wiersz jest zgłaszane jako błąd kompilacji:

Protocol 'ProtocolWithAlias' can only be used as a generic constraint because it has Self os associated type requirements 

co oznacza, że ​​nie można używać ProtocolWithAlias jako konkretny typ, co z kolei oznacza, że ​​nie można zadeklaruj zmienne mające typ ProtocolWithAlias, a zatem nie ma sensu definiować funkcji zwracającej je.

Nie mogę znaleźć żadnej wzmianki na ten temat w oficjalnej dokumentacji, ale jestem pewien, że gdzieś czytam, po prostu nie pamiętam gdzie.

Wniosek: ja interpretować błąd masz kłopoty z:

SimpleTrain<T> is not convertible to 'ShortType' 

jako bezpośrednią konsekwencją protokołu jest typealiased.

Pamiętaj, że jest to moja osobista opinia, ponieważ obecnie nie mogę tego udowodnić, oprócz tego protokołu testowego kodu kodu z aliasami i bez nich.

+0

Dziękuję za to Antonio. Chociaż jest to zdecydowanie praktyczne rozwiązanie (i takie, którego użyję, jeśli nie jest lepsze), uważam, że wyraźny downcast nie powinien być wymagany w sytuacji, w której kompilator rzeczywiście może wnioskować, że przypisany typ dopasowuje zmienną. – drasto

+0

Proszę przeczytać zaktualizowaną odpowiedź - nie wiem, czy to ma sens :) – Antonio

+0

Poprzez osobiste, jest to bardzo dobra opinia i analizy rzeczywiście. Ponadto, jeśli będziemy kontynuować tę kwestię: protokoły Typealiased zastępują ogólne protokoły (inżynierowie Apple deklarują, że są * lepsi *).Twoje wnioski oznaczają, że nie mogą być używane jako typy (stałych, zmiennych, metod ...). W języku Java lub C# jest odpowiednikiem tego, że można używać tylko typów niegenetycznych jako interfejsów. Jeśli te analizy są poprawne, to jest to poważne zaniedbanie IMHO w Swift. Byłbym bardzo zainteresowany uzyskaniem opinii kogoś, kto zasługuje na Swift w tej sprawie. – drasto

0

Czy masz mieć używać protokołu? Nie mam zbyt dobrej znajomości teorii typów, ale wydaje się, że problem powraca do tematu "typów wyższych typów", w dyskusji na temat wątku Apple i od góry.

Mimo to, jeśli można spróbować zbliżyć użyciu abstrakcyjnej klasy bazowej (z zastrzeżeniem, że jest to faktycznie nie abstrakcyjne i nie działa na elemencie), w następującej postaci:

class AnyTrain<CarriageType> { 

    func addCarriage(carriage: CarriageType) {} 
    func shortTrain() -> AnyTrain<CarriageType> { return AnyTrain<CarriageType>() } 
} 

class SimpleTrain<T> : AnyTrain<T>, Printable { 
    private var carriages: [T] = [T]() 

    override func addCarriage(carriage: T) { 
     carriages.append(carriage) 
    } 

    override func shortTrain() -> AnyTrain<T> { 
     let short = SimpleTrain<T>() 
     short.addCarriage(carriages[0]) 
     return short //COMPILE ERROR: SimpleTrain<T> is not convertible to 'ShortType' 
    } 

    var description:String { 
     return "Train: \(carriages)" 
    } 
} 


let train = SimpleTrain<Int>() 
train.addCarriage(1) 
train.addCarriage(2) 
train.addCarriage(3) 

let shortTrain = train.shortTrain() 
println(shortTrain) 

// obviously you can refer to it as the base 
let anotherTrain:AnyTrain<Int> = SimpleTrain<Int>() 
anotherTrain.addCarriage(3) 
anotherTrain.addCarriage(2) 
anotherTrain.addCarriage(1) 

let anotherShortTrain = anotherTrain.shortTrain() 
println(anotherShortTrain) 

ten wyjścia:

Train: [1]

Train: [3]

mam eksperymentowali z tą techniką, w przypadku gdy mam abstrakcyjnych hierarchie gdzie jeden typ jest wspólne, a inne innymi różni ale nie wpływa podpisy funkcję. Używając "klasy abstrakcyjnej", mogę utworzyć tablicę "dowolnego typu z tym wspólnym typem generycznym (podczas gdy inne mogą się różnić)"

Powiązane problemy