2014-09-05 16 views
6

Jest to kontynuacja pytania: Protocol func returning Self. Protokół jest następujący:Typy protokołów Swift i zwracania w funkcjach globalnych

protocol Copyable { 
    init(copy: Self) 
    func copy() -> Self 
} 

Poniższe działa dobrze, ale funkcja copy() jest dokładnie taka sama dla każdego wdrożenia, mianowicie

func copy() -> Self { 
    return self.dynamicType(copy: self) 
} 

Zgodnie z tym http://nshipster.com/swift-default-protocol-implementations/ Próbowałem globalną func

func copy<T : Copyable>(makeCopy: T) -> T { 
    return makeCopy.dynamicType(copy: makeCopy) 
} 

Jednak, gdy zostanie wywołana w klasie implementującej poniższy protokół

Otrzymuję błąd zgodnie z opisem. Po wpisaniu mutated autouzupełnianie pokazuje mutated jako (C) i nie mam pojęcia, co to oznacza. Próbowałem również dodać required do func mutated(), ale pozornie required jest dozwolone tylko dla inits. Jakikolwiek sposób, aby to zadziałało?

Odpowiedz

1

To pytanie ma tę samą formę, co kopia i to samo rozwiązanie. Uczyń mutację raczej inicjatorem niż metodą.

protocol Copyable { 
    init(copy: Self) 
} 

protocol Mutatable : Copyable { 
    init(byMutating: Self) 
} 

class C : Mutatable { 
    var a = 0 

    required init(_ a: Int) { 
    self.a = a 
    } 

    required init(copy: C) { 
    a = copy.a 
    } 

    required convenience init(byMutating: C) { 
    self.init(copy: byMutating) 
    self.a++ 
    } 
} 

// These are purely for convenience 
func copy<T : Copyable>(x: T) -> T { 
    return x.dynamicType(copy: x) 
} 

func mutated<T: Mutatable>(x: T) -> T { 
    return x.dynamicType(byMutating: x) 
} 

Ale powtórzyć punkt Mattt w powiązanym artykule, można mieć składnię C(copy: x) dość wygodnie, i można mieć składnię copy(x) całkiem wygodnie, i zawsze jest x.dynamicType(copy: x). Ale nie można mieć składni x.copy() bez irytującej pracy. Musisz albo duplikować func copy() -> Self { return copy(self) } w każdej klasie, albo musisz stworzyć jakąś konkretną klasę, która implementuje tę metodę i ostatecznie dziedziczy C. Jest to obecnie podstawowe ograniczenie Swift. Zgadzam się z diagnozą możliwych rozwiązań przez Mattta i podejrzewam, że jakiś system cech, prawdopodobnie według Scali, prawdopodobnie zostanie dodany w przyszłości.

Warto się skupić na komentarzu Mattta, że ​​"wszystko to podkreśla znaczące napięcie między metodami i funkcjami w Swift". Jest to kolejny sposób powiedzenia, że ​​istnieją napięcia między paradygmatem obiektowym a paradygmatem funkcjonalnym, a poruszanie się między nimi może powodować pewne rozłączenia. Języki starają się opisywać ten problem różnymi funkcjami, ale istnieją istotne różnice między obiektami z wiadomościami i właściwościami, a funkcjami z danymi i kombinatorami, a "uzyskiwanie najlepszych z obu światów" może czasami powodować pewne ostre krawędzie.

Łatwo jest zapomnieć, porównując Swift z innymi językami, że istnieje duża różnica między wersjami 0.9 i 2.2. Wiele rzeczy, które przyjmujemy za pewnik w naszych ulubionych językach, również nie istniało w ich wersjach v1.


Do Twojego komentarza, można myśleć, że mutated jest typu Self. Ale jest to typ C, jak wskazuje autouzupełnianie. Tak jak poprzednio, C to nie to samo co Self, chyba że możesz obiecać, że nie ma podklas (C jest albo final lub struct). Typy Swift są rozwiązywane podczas kompilacji, a nie w czasie wykonywania, chyba że używasz dynamicType.

być trochę bardziej szczegółowe, Swift patrzy na tej linii:

let mutated = copy(self) 

Zauważa, że ​​copy jest rodzajowy od typu jej parametr i musi skonstruować wersję copyw czasie kompilacji, aby zadzwonić. Nie ma typu Self. To tylko symbol zastępczy i musi zostać rozwiązany podczas kompilacji. Typ self w tym zakresie leksykalnym to C. Więc konstruuje copy<C>. Ale jeśli podklasowałeś C, może to być zła funkcja (w tym przypadku byłaby). Jest to bardzo blisko związane z: https://stackoverflow.com/a/25549841/97337.

Fakt, że autouzupełnianie typu mówi: (C), a nie C, jest niewielkim efektem ubocznym funkcjonowania i funkcjonalności Swift i pojawia się dość regularnie, ale jeszcze nie spotkałem się z przypadkiem, w którym to naprawdę miało znaczenie. Funkcja Swift, taka jak func f(x: Int, y:Int), nie ma w rzeczywistości dwóch parametrów. Ma jeden parametr 2-towy typu (Int, Int). Fakt ten jest ważny dla funkcjonowania składni curry (zobacz Swift Programming Language, aby dowiedzieć się więcej o curry w Swift). Więc kiedy specjalizujesz się w copy, wyspecjalizowałeś ją w 1-kodzie typu (C). (Możliwe, że kompilator próbuje to zrobić jako jedną z wielu prób, i to jest właśnie ta, o której donosi.) W Swift każda wartość może zostać zmiennie wymieniana na 1-tkę tego samego typu. Tak więc zwrot copy jest tak naprawdę 1-krotną wersją C, napisaną (C). Podejrzewam, że kompilator Swift poprawi swoje wiadomości z czasem, aby usunąć obce nawiasy, ale dlatego czasami się pojawiają.

+0

Ok, więc używaj inicjalizatorów do wszystkiego. Rozumiem. Dzięki jeszcze raz! – aeubanks

+0

Moje pytanie brzmi: dlaczego oryginalny kod nie działa, jeśli 'copy (T) -> T' zwraca klasę, która go nazwała? W funkcji 'mutated()' powinien zwracać 'Self' w prawo? Nawet jeśli jest lepszy sposób na zrobienie tego, nadal jestem ciekawy, jak działa Swift. – aeubanks

+0

zaktualizowano o więcej informacji –

Powiązane problemy