Swift Generics: la versión de IntegerType funciona pero no FloatingPointType

Escribí dos versiones de un algorithm para convolución discreta utilizando generics Swift. La versión entera funciona. Pero la versión de punto flotante tiene un problema con la multiplicación:

import Foundation import Swift func linconv<T: IntegerType>(signal_A signal_A: [T], signal_B: [T]) -> [T]? { // guard guard signal_A.isEmpty == false && signal_B.isEmpty == false else { return nil } // reverse at least one of the arrays //let signal_A_reversed = Array(signal_A.reverse()) // size of new array let N = signal_A.count + signal_B.count - 1 // new array for result var resultSignal = [T](count: N, repeatedValue: 0) for n in 0..<N { for j in 0...n { if j < signal_B.count && (n - j) < signal_A.count { resultSignal[n] += signal_B[j] * signal_A[n - j] } } } return resultSignal } func linconv<T: FloatingPointType>(signal_A signal_A: [T], signal_B: [T]) -> [T]? { // guard guard signal_A.isEmpty == false && signal_B.isEmpty == false else { return nil } // reverse at least one of the arrays //let signal_A_reversed = Array(signal_A.reverse()) // size of new array let N = signal_A.count + signal_B.count - 1 // new array for result var resultSignal = [T](count: N, repeatedValue: T(0)) for n in 0..<N { for j in 0...n { if j < signal_B.count && (n - j) < signal_A.count { resultSignal[n] += signal_B[j] * signal_A[n - j] // compiler says error here! } } } return resultSignal } 

Para la versión FloatingPointType, el comstackdor dice "Operador binary '*' no se puede aplicar a dos 'operandos T'". Pero no hace esto en la versión IntegerType. ¿Por qué?

El protocolo FloatingPointType es adoptado por los types de Double y Float , pero por el contrario, el protocolo, por alguna razón, no incluye planos para los methods del operador, como (en su caso), * operador binary o += operador de asignación. Tenga en count aquí la importancia de que solo porque algunos types conocidos adopten un protocolo, ese protocolo en sí mismo no debe contener necesariamente todos los planos que esperamos para los types que lo han adoptado.

El protocolo IntegerType , por otro lado, incluye planos para los methods de operador.

Por lo tanto, su T genérico que cumple con el protocolo FloatingPointType no es necesariamente (a los ojos de Swift) multiplicable, y así sucesivamente, ya que el protocolo no incluye planos para tales operaciones. Si observamos la reference de biblioteca estándar para FloatingPointType , vemos que aparentemente solo se adopta con Double , Float (y CGFloat ). Sabemos que todos estos tres types funcionan bien, por su count, con nuestros operadores habituales, entonces, ¿por qué no podemos usar esos operadores en un formatting genérico conforme a FloatingPointType ? Una vez más, la colección de types que se conforman a un protocolo realmente no da idea de qué planos contiene ese protocolo .

Como ejemplo, mire el siguiente protocolo la extensión de algunos types fundamentales para ajustarse a él

 protocol ReallyLotsOfAdditionalStuff {} extension Int : ReallyLotsOfAdditionalStuff {} extension Double : ReallyLotsOfAdditionalStuff {} 

La reference de la biblioteca para este protocolo ficticio enumera que solo los types Int y Double adoptan . Por el contrario, si no somos cuidadosos, podríamos esperar que los generics que se ajusten al protocolo ReallyLotsOfAdditionalStuff lo less, agregables (además de muchas cosas adicionales), pero naturalmente, este no es el caso.


De todos modos, puede solucionar esto usted mismo, sin embargo, creando un nuevo protocolo que agregue como una restricción de tipo adicional para la T genérica en su function FloatingPointType .

 protocol MyNecessaryFloatingPointTypeOperations { func *(lhs: Self, rhs: Self) -> Self func += (inout lhs: Self, rhs: Self) // ... other necessary floating point operator blueprints ... } extension Float: MyNecessaryFloatingPointTypeOperations {} extension Double: MyNecessaryFloatingPointTypeOperations {} // Example: only type constraint to FloatingPointType func errorFloatingPointType<T: FloatingPointType> (a: T, b: T) -> T { return a * b // Error: binary operator '*' cannot be applied to two 'T' operands } // Example: additional type constraint to your custom protocol func noErrorFloatingPointType<T: protocol<FloatingPointType, MyNecessaryFloatingPointTypeOperations>> (a: T, b: T) -> T { return a * b // ok! } 

Por lo tanto, para arreglar su FloatingPointType , agregue su protocolo personalizado como una restricción de tipo adicional para T en el encabezado de la function:

 func linconv<T: protocol<FloatingPointType, MyNecessaryFloatingPointTypeOperations>>(signal_A: [T], signal_B: [T]) -> [T]? { // ... } 

Alternativamente, permita que su propio protocolo henetworkinge FloatingPointType y agregue los methods adicionales necesarios para su "protocolo hijo", por ejemplo:

 protocol ImprovedFloatingPointType : FloatingPointType { func *(lhs: Self, rhs: Self) -> Self func += (inout lhs: Self, rhs: Self) // ... other necessary integer and floating point blueprints } extension Float: ImprovedFloatingPointType {} extension Double: ImprovedFloatingPointType {} func linconv<T: ImprovedFloatingPointType>(signal_A: [T], signal_B: [T]) -> [T]? { // ... } 

Finalmente, podríamos preguntarnos, ¿incluso necesitamos el protocolo FloatingPointType en primer lugar (incluso como protocolo principal para nuestro protocolo personalizado)? Si solo queremos hacer un genérico para manejar los dos types de coma flotante rápida Double y Float , entonces también deberíamos aplicar solo el protocolo MyNecessaryFloatingPointTypeOperations como una restricción de tipo a nuestro genérico:

 func myFloatingPointGenericFunction<T: MyNecessaryFloatingPointTypeOperations> (a: T, b: T) -> T { // ... return a * b } 

Como ya sabrá, necesitamos que el genérico se ajuste al protocolo FloatingPointType para un solo model: para determinar nuestra function genérica que podemos inicializar instancias T utilizando un iniciador de integers, concretamente init(_ value: Int) . Por ejemplo, en su function:

 // new array for result var resultSignal = [T](count: N, repeatedValue: T(0)) // <-- 

Sin embargo, si este es el único anteproyecto que estamos utilizando desde el protocolo FloatingPointType , también podríamos agregarlo como un anteproyecto a nuestro propio protocolo, y eliminar la restricción de tipo genérico para FloatingPointType completo.

 protocol MyNecessaryFloatingPointTypeOperations { func *(lhs: Self, rhs: Self) -> Self func += (inout lhs: Self, rhs: Self) init(_ value: Int) // ... other necessary floating point blueprints } extension Float: MyNecessaryFloatingPointTypeOperations {} extension Double: MyNecessaryFloatingPointTypeOperations {} func myFloatingPointGenericFunction<T: MyNecessaryFloatingPointTypeOperations> (a: T, b: T) -> T { // ... var c = T(0) // OK c += a * b // OK return c } 

Con esto, nos damos count de que realmente no necesitamos dos generics por separado para los types integers y los types de coma flotante. Como nosotros (para su ejemplo) solo necesitamos 1. un by-int-initializer, 2. * operador binary y 3. + = operador de asignación , podríamos build un genérico para todos los types que se ajusten a estas marcas azules como una restricción de tipo , y extender los types que deseamos que estén cubiertos por nuestro genérico por este protocolo.

 protocol MyCustomProtocol { func *(lhs: Self, rhs: Self) -> Self func += (inout lhs: Self, rhs: Self) init(_ value: Int) // ... other necessary integer and floating point blueprints } extension Int: MyCustomProtocol {} extension Float: MyCustomProtocol {} extension Double: MyCustomProtocol {} func myIntAndFloatGenericFunction<T: MyCustomProtocol> (a: T, _ b: T) -> T { // ... var c = T(0) // OK c += a * b // OK return c } let aInt = 2 let bInt = 3 let aInt32: Int32 = 2 let bInt32: Int32 = 3 let aDouble = 2.5 let bDouble = 3.0 let cInt = myIntAndFloatGenericFunction(aInt, bInt) // 6 let cInt32 = myIntAndFloatGenericFunction(aInt32, bInt32) // error let cDouble = myIntAndFloatGenericFunction(aDouble, bDouble) // 7.5 

Aquí, sin embargo, vemos una ganancia de utilizar el protocolo IntegerType existente: ya es adoptado por numerosos types de integers, mientras que para nuestro protocolo personalizado, todos estos types int (si queremos usarlos en nuestro genérico) deben ser explícitamente Extendido para adoptar nuestro protocolo personalizado.

Para resumirlo: si sabe explícitamente qué types desea que su genérico cubra, hágale escribir su propio protocolo personalizado y amplíe estos types para adaptarse a esto. Si quiere todos (numerosos) types integers diferentes, es IntegerType usar dos generics separados para ints y IntegerType , con el protocolo IntegerType para este último.