Restrições de tipos múltiplos no Swift

133

Digamos que eu tenho estes protocolos:

protocol SomeProtocol {

}

protocol SomeOtherProtocol {

}

Agora, se eu quiser uma função que aceite um tipo genérico, mas esse tipo deva estar em conformidade, SomeProtocoleu poderia fazer:

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

Mas existe uma maneira de adicionar uma restrição de tipo para vários protocolos?

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

}

Coisas semelhantes usam vírgulas, mas, neste caso, iniciariam a declaração de um tipo diferente. Aqui está o que eu tentei.

<T: SomeProtocol | SomeOtherProtocol>
<T: SomeProtocol , SomeOtherProtocol>
<T: SomeProtocol : SomeOtherProtocol>
Logan
fonte
Esta é uma questão especialmente relevante como os docs Swift não mencionar isso no capítulo genéricos ...
de Bruno Philipe

Respostas:

241

Você pode usar uma cláusula where que permite especificar quantos requisitos você deseja (todos os quais devem ser atendidos) separados por vírgulas

Swift 2:

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

Swift 3 e 4:

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

ou a cláusula where mais poderosa:

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

É claro que você pode usar a composição do protocolo (por exemplo, protocol<SomeProtocol, SomeOtherProtocol>), mas é um pouco menos flexível.

Usar wherepermite lidar com casos em que vários tipos estão envolvidos.

Você ainda pode compor protocolos para reutilização em vários locais ou apenas atribuir um nome significativo ao protocolo composto.

Swift 5:

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

Isso parece mais natural, pois os protocolos estão próximos ao argumento.

Jiaaro
fonte
Nossa, isso não é lógico, mas é bom saber que eu só quero ser um dos spammers agradecidos por esse, não percebi isso em um mês desde que eu precisava.
Mathijs Segers
3
Existe alguma maneira de fazer a mesma coisa com classes e estruturas na expressão contraint tipo? por exemplo <T where T:SomeStruct, T:AnotherStruct>? Para classes, o compilador parece interpretar isso como dizendo "T é uma subclasse de ambos" e, para estruturas, apenas reclama isso "Type 'T' constrained to non-protocol type".
Jarrod Smith
Para o exemplo específico na composição do protocolo de perguntas do OP: s deve ser o método preferível: a solução acima é válida, mas, imho, desorganiza desnecessariamente a assinatura da função. Além disso, o uso da composição de protocolo como, por exemplo, uma restrição de tipo, ainda permite que você use a wherecláusula para tipo / outro uso adicional, por exemplo, func someFunc<U, T: protocol<SomeProtocol, SomeOtherProtocol> where T.SubType == U>(arg: T, arg2: U) { ... }para tipealias SubTypeem, por exemplo SomeProtocol.
DFRI
1
Parece que isso foi preterido no swift3 e recomenda o uso de: func someFunc <T> (arg: T) em que T: SomeProtocol, T: SomeOtherProtocol {
Cristi Băluță
2
Existe uma maneira de dizer rapidamente que T precisa ser de um determinado tipo de objeto E implementar um determinado protocolo?
Georg
73

Você tem duas possibilidades:

  1. Você usa uma cláusula where conforme indicado na resposta de Jiaaro:

    func someFunc<T where T : SomeProtocol, T : SomeOtherProtocol>(arg: T) {
        // do stuff
    }
  2. Você usa um tipo de composição de protocolo :

    func someFunc<T : protocol<SomeProtocol, SomeOtherProtocol>>(arg: T) {
        // do stuff
    }
Jean-Philippe Pellet
fonte
2
imo a segunda solução é mais bonita, eu iria para esta resposta também é mais completa apresentando duas opções
Mathijs Segers
2
O número 2 é o único que funciona para mim no Swift 2 ao declarar a typealias. Obrigado!
Bruno Philipe
19

A evolução para o Swift 3.0 traz algumas mudanças. Nossas duas opções agora parecem um pouco diferentes.

Usando uma wherecláusula no Swift 3.0:

A wherecláusula agora foi movida para o final de uma assinatura de função para melhorar a legibilidade. Portanto, a herança de vários protocolos agora se parece com isso:

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

}

Usando a protocol<>construção no Swift 3.0:

A composição usando a protocol<>construção está sendo preterida. O anterior protocol<SomeProtocol, SomeOtherProtocol>agora se parece com isso:

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

}

Referências.

Mais informações sobre as alterações whereestão aqui: https://github.com/apple/swift-evolution/blob/master/proposals/0081-move-where-expression.md

E, mais sobre as mudanças para a construção do protocolo <> estão aqui: https://github.com/apple/swift-evolution/blob/master/proposals/0095-any-as-existential.md

ncke
fonte
13

O Swift 3 oferece até três maneiras diferentes de declarar sua função.

protocol SomeProtocol {
    /* ... */
}

protocol SomeOtherProtocol {
    /* ... */        
}

1. Usando &operador

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

2. whereCláusula Using

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

3. Usando wherecláusula e &operador

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

Observe também que você pode usar typealiaspara reduzir sua declaração de função.

typealias RequiredProtocols = SomeProtocol & SomeOtherProtocol

func someFunc<T: RequiredProtocols>(arg: T) {
    /* ... */   
}
Imanou Petit
fonte