Restricciones de tipo múltiple en Swift

133

Digamos que tengo estos protocolos:

protocol SomeProtocol {

}

protocol SomeOtherProtocol {

}

Ahora, si quiero una función que tome un tipo genérico, pero ese tipo debe cumplir SomeProtocol, podría hacer:

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

Pero, ¿hay alguna manera de agregar una restricción de tipo para múltiples protocolos?

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

}

Cosas similares usan comas, pero en este caso, comenzaría la declaración de un tipo diferente. Esto es lo que he intentado.

<T: SomeProtocol | SomeOtherProtocol>
<T: SomeProtocol , SomeOtherProtocol>
<T: SomeProtocol : SomeOtherProtocol>
Logan
fuente
Esta es una pregunta especialmente relevante ya que los documentos de Swift no mencionan esto en el capítulo de genéricos ...
Bruno Philipe

Respuestas:

241

Puede usar una cláusula where que le permite especificar tantos requisitos como desee (todos los cuales deben cumplirse) separados por comas

Swift 2:

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

Swift 3 y 4:

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

o la cláusula where más poderosa:

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

Por supuesto, puede usar la composición del protocolo (por ejemplo, protocol<SomeProtocol, SomeOtherProtocol>), pero es un poco menos flexible.

El uso le wherepermite tratar casos en los que intervienen varios tipos.

Es posible que aún desee componer protocolos para su reutilización en varios lugares, o simplemente para dar al protocolo compuesto un nombre significativo.

Swift 5:

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

Esto se siente más natural ya que los protocolos están al lado del argumento.

Jiaaro
fuente
Cielos, esto no es lógico, pero es bueno saber que solo quiero ser uno de los spammers de agradecimiento por esto, no me he dado cuenta de esto en un mes desde que lo necesitaba.
Mathijs Segers
3
¿Alguna forma de hacer lo mismo con clases y estructuras en la expresión de tipo contraint? por ejemplo <T where T:SomeStruct, T:AnotherStruct>? Para las clases, el compilador parece interpretar esto como diciendo "T es una subclase de ambos", y para las estructuras simplemente se queja de eso "Type 'T' constrained to non-protocol type".
Jarrod Smith
Para el ejemplo específico en la OP: la composición del protocolo de preguntas debería ser un método preferible: la solución anterior es válida, pero, en mi opinión, satura innecesariamente la firma de la función. Además, el uso de la composición del protocolo como, por ejemplo, una restricción de tipo, todavía le permite usar la wherecláusula para el uso adicional de tipos / otros, por ejemplo, func someFunc<U, T: protocol<SomeProtocol, SomeOtherProtocol> where T.SubType == U>(arg: T, arg2: U) { ... }para los typealias SubTypeen, por ejemplo SomeProtocol.
dfri
1
Parece que esto está en desuso en swift3 y recomienda usar: func someFunc <T> (arg: T) donde T: SomeProtocol, T: SomeOtherProtocol {
Cristi Băluță
2
¿Hay alguna manera de decirle a Swift que T debe ser de cierto tipo de objeto e implementar un cierto protocolo?
Georg
73

Tienes dos posibilidades:

  1. Utiliza una cláusula where como se indica en la respuesta de Jiaaro:

    func someFunc<T where T : SomeProtocol, T : SomeOtherProtocol>(arg: T) {
        // do stuff
    }
  2. Utiliza un tipo de composición de protocolo :

    func someFunc<T : protocol<SomeProtocol, SomeOtherProtocol>>(arg: T) {
        // do stuff
    }
Jean-Philippe Pellet
fuente
2
En mi opinión, la segunda solución es más bonita, iría por esta respuesta, también es más completa presentando dos opciones
Mathijs Segers
2
El número 2 es el único que funciona para mí en Swift 2 cuando declaro a typealias. ¡Gracias!
Bruno Philipe
19

La evolución a Swift 3.0 trae algunos cambios. Nuestras dos opciones ahora se ven un poco diferentes.

Usando una wherecláusula en Swift 3.0:

La wherecláusula ahora se ha movido al final de una firma de función para mejorar la legibilidad. Entonces, la herencia de múltiples protocolos ahora se ve así:

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

}

Usando la protocol<>construcción en Swift 3.0:

La composición que usa la protocol<>construcción está en desuso. Lo anterior protocol<SomeProtocol, SomeOtherProtocol>ahora se ve así:

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

}

Referencias

Aquí encontrará más información sobre los cambios where: https://github.com/apple/swift-evolution/blob/master/proposals/0081-move-where-expression.md

Y, más sobre los cambios para la construcción del protocolo <> están aquí: https://github.com/apple/swift-evolution/blob/master/proposals/0095-any-as-existential.md

ncke
fuente
13

Swift 3 ofrece hasta 3 formas diferentes de declarar su función.

protocol SomeProtocol {
    /* ... */
}

protocol SomeOtherProtocol {
    /* ... */        
}

1. Usando &operador

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

2. Usando la wherecláusula

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

3. Usando wherecláusula y &operador

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

También tenga en cuenta que puede usar typealiaspara acortar su declaración de función.

typealias RequiredProtocols = SomeProtocol & SomeOtherProtocol

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