Wiele ograniczeń typu w języku Swift

133

Powiedzmy, że mam te protokoły:

protocol SomeProtocol {

}

protocol SomeOtherProtocol {

}

Teraz, jeśli chcę funkcję, która przyjmuje typ ogólny, ale ten typ musi być zgodny z, SomeProtocolmogę zrobić:

func someFunc<T: SomeProtocol>(arg: T) {
    // do stuff
}

Ale czy istnieje sposób na dodanie ograniczenia typu dla wielu protokołów?

func bothFunc<T: SomeProtocol | SomeOtherProtocol>(arg: T) {

}

Podobne rzeczy używają przecinków, ale w tym przypadku rozpoczęłoby to deklarację innego typu. Oto, czego próbowałem.

<T: SomeProtocol | SomeOtherProtocol>
<T: SomeProtocol , SomeOtherProtocol>
<T: SomeProtocol : SomeOtherProtocol>
Logan
źródło
Jest to szczególnie istotne pytanie, ponieważ dokumentatorzy Swift nie wspominają o tym w rozdziale o rodzajach ogólnych ...
Bruno Philipe,

Odpowiedzi:

241

Możesz użyć klauzuli where, która pozwala określić dowolną liczbę wymagań (z których wszystkie muszą być spełnione) oddzielonych przecinkami

Swift 2:

func someFunc<T where T:SomeProtocol, T:SomeOtherProtocol>(arg: T) {
    // stuff
}

Swift 3 i 4:

func someFunc<T: SomeProtocol & SomeOtherProtocol>(arg: T) {
    // stuff
}

lub mocniejsza klauzula where:

func someFunc<T>(arg: T) where T:SomeProtocol, T:SomeOtherProtocol{
    // stuff
}

Możesz oczywiście użyć kompozycji protokołu (np. protocol<SomeProtocol, SomeOtherProtocol>), Ale jest to trochę mniej elastyczne.

Używanie wherepozwala radzić sobie z przypadkami, w których zaangażowanych jest wiele typów.

Nadal możesz tworzyć protokoły do ​​ponownego wykorzystania w wielu miejscach lub po prostu nadać złożonemu protokołowi znaczącą nazwę.

Swift 5:

func someFunc(arg: SomeProtocol & SomeOtherProtocol) { 
    // stuff
}

Wydaje się to bardziej naturalne, ponieważ protokoły są obok argumentu.

Jiaaro
źródło
Rany, to nie jest logiczne, ale dobrze wiedzieć, że chcę być jednym z tych spamerów, którzy dziękują za to, nie zdawałem sobie z tego sprawy w ciągu miesiąca, odkąd tego potrzebowałem.
Mathijs Segers
3
Czy jest jakiś sposób, aby zrobić to samo z klasami i strukturami w wyrażeniu ograniczenia typu? np. <T where T:SomeStruct, T:AnotherStruct>? W przypadku klas kompilator wydaje się interpretować to jako stwierdzenie, że „T jest podklasą obu”, aw przypadku struktur po prostu narzeka "Type 'T' constrained to non-protocol type".
Jarrod Smith
Dla konkretnego przykładu w OP: s skład protokołu pytania powinien być metodą preferowaną: powyższe rozwiązanie jest poprawne, ale, imho, niepotrzebnie zaśmieca podpis funkcji. Ponadto użycie kompozycji protokołu jako, np. Ograniczenia typu, nadal pozwala na użycie whereklauzuli dla dodatkowego typu / innego użycia, np. func someFunc<U, T: protocol<SomeProtocol, SomeOtherProtocol> where T.SubType == U>(arg: T, arg2: U) { ... }Dla aliasów typu SubTypew np SomeProtocol.
dfri
1
Wygląda na to, że jest to przestarzałe w swift3 i zaleca użycie: func someFunc <T> (arg: T) gdzie T: SomeProtocol, T: SomeOtherProtocol {
Cristi Băluță
2
Czy istnieje sposób, aby powiedzieć swiftowi, że T musi być określonego typu obiektu ORAZ zaimplementować określony protokół?
Georg
73

Masz dwie możliwości:

  1. Używasz klauzuli gdzie wskazano w odpowiedzi Jiaaro:

    func someFunc<T where T : SomeProtocol, T : SomeOtherProtocol>(arg: T) {
        // do stuff
    }
  2. Używasz typu kompozycji protokołu :

    func someFunc<T : protocol<SomeProtocol, SomeOtherProtocol>>(arg: T) {
        // do stuff
    }
Jean-Philippe Pellet
źródło
2
imo drugie rozwiązanie jest ładniejsze, wybrałbym tę odpowiedź, jest też bardziej kompletna prezentując dwie opcje
Mathijs Segers
2
Numer 2 jest jedynym, który działa dla mnie w Swift 2, gdy deklaruję plik typealias. Dzięki!
Bruno Philipe
19

Ewolucja do Swift 3.0 przynosi pewne zmiany. Nasze dwie opcje wyglądają teraz trochę inaczej.

Korzystanie z whereklauzuli w Swift 3.0:

whereKlauzula teraz przeniósł się do końca podpisu funkcji w celu poprawienia czytelności. Tak więc dziedziczenie wielu protokołów wygląda teraz następująco:

func someFunc<T>(arg: T) where T:SomeProtocol, T:SomeOtherProtocol {

}

Korzystanie z protocol<>konstrukcji w Swift 3.0:

Kompozycja korzystająca z protocol<>konstrukcji jest przestarzała. Wcześniej protocol<SomeProtocol, SomeOtherProtocol>wygląda teraz tak:

func someFunc<T:SomeProtocol & SomeOtherProtocol>(arg: T) {

}

Bibliografia.

Więcej informacji na temat zmian dla wherejest tutaj: https://github.com/apple/swift-evolution/blob/master/proposity/0081-move-where-expression.md

Więcej informacji na temat zmian w konstrukcji protokołu <> można znaleźć tutaj: https://github.com/apple/swift-evolution/blob/master/propeals/0095-any-as-existential.md

ncke
źródło
13

Swift 3 oferuje do 3 różnych sposobów deklarowania swojej funkcji.

protocol SomeProtocol {
    /* ... */
}

protocol SomeOtherProtocol {
    /* ... */        
}

1. Korzystanie z &operatora

func someFunc<T: SomeProtocol & SomeOtherProtocol>(arg: T) {
    /* ... */
}

2. Korzystanie z whereklauzuli

func someFunc<T>(arg: T) where T: SomeProtocol, T: SomeOtherProtocol {
    /* ... */
}

3. Stosowanie whereklauzuli i &operatora

func someFunc<T>(arg: T) where T: SomeProtocol & SomeOtherProtocol {
    /* ... */        
}

Zauważ również, że możesz użyć typealias, aby skrócić deklarację funkcji.

typealias RequiredProtocols = SomeProtocol & SomeOtherProtocol

func someFunc<T: RequiredProtocols>(arg: T) {
    /* ... */   
}
Imanou Petit
źródło