Swift泛型:IntegerType版本工作但不是FloatingPointType

时间:2015-12-24 22:03:24

标签: ios swift macos generics foundation

我使用Swift泛型键入了两个版本的离散卷积算法。整数版本有效。但是浮点版本存在乘法问题:

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
}

对于FloatingPointType版本,编译器说“二进制运算符'*'不能应用于两个'T'操作数”。但是,它不会在IntegerType版本上执行此操作。为什么呢?

1 个答案:

答案 0 :(得分:5)

<{em> FloatingPointTypeDouble类型确实采用了Float协议,但相反,协议由于某种原因不包括用于运算符方法的蓝图,例如(在您的情况下),*二元运算符或+=赋值运算符。请注意,仅仅因为某些已知类型采用协议的重要性,该协议本身不一定包含我们对采用它的那些类型所期望的所有蓝图。

另一方面,IntegerType协议包括运算符方法的蓝图。

因此,符合协议T的通用FloatingPointType不一定(在swift眼中)可以乘法等等,因为协议不包含此类操作的蓝图。如果我们查看standard library reference for FloatingPointType,我们会发现它似乎仅由 DoubleFloat(和CGFloat)采用。我们知道所有这三种类型都可以通过我们的常规操作员自行运行,所以嘿,为什么我们不能在符合FloatingPointType的通用符号上使用这些运算符?同样,符合协议的类型集合实际上无法深入了解协议包含的蓝图

作为一个例子,请查看以下协议中某些基本类型的扩展以符合它

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

此虚拟协议的库参考将列出仅IntDouble 采用类型的类型。相反,如果我们不小心,我们可以期望符合协议ReallyLotsOfAdditionalStuff的泛型至少可以说是可添加的(除了许多额外的东西),但自然,情况并非如此。

无论如何,您可以自行修复此问题,方法是创建一个新协议,作为T函数中通用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!
}

因此,要修复您的FloatingPointType,请在函数标题中将自定义协议添加为T的附加类型约束:

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

或者,让您自己的协议继承FloatingPointType并添加您的&#34;子协议所需的任何其他方法&#34;,例如:

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]? { 
    // ...
}

最后,我们可能会问,我们是否首先需要FloatingPointType协议(即使作为我们自定义协议的父协议)?如果我们只想为处理两个快速浮点类型DoubleFloat制作泛型,那么我们也可以只将协议MyNecessaryFloatingPointTypeOperations作为类型约束应用于我们的泛型:< / p>

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

正如您可能已经知道的那样,我们需要通用符合单个蓝图的FloatingPointType协议:确定我们的泛型函数,我们可以使用整数初始化器初始化T个实例,即{{ 1}}。例如,在你的职能中:

init(_ value: Int)

但是,如果这是我们在// new array for result var resultSignal = [T](count: N, repeatedValue: T(0)) // <-- 协议中使用的唯一蓝图,我们不妨将其作为蓝图添加到我们自己的协议中,并将通用类型约束移除到{{ 1}}完全。

FloatingPointType

有了这个,我们意识到我们并不需要为整数类型和浮点类型分别使用两个单独的泛型。因为我们(对于你的例子)只需要 1。一个by-int-initializer,2. *二元运算符,和3. + =赋值运算符,我们可以为符合这些蓝色标记的所有类型构造一个泛型作为类型约束,并扩展我们的类型希望通过此协议涵盖我们的通用。

FloatingPointType

然而,在这里,我们看到了使用现有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 } 协议的一个好处:它已经被许多整数类型所采用,而对于我们的自定义协议,所有这些int类型(如果我们想要在它们中使用它们)我们的通用)需要明确扩展以采用我们的自定义协议。

要将其包装起来:如果您明确知道要通用的覆盖哪些类型,您可能会膨胀编写自己的自定义协议并扩展这些类型以适应这一点。如果你想要所有(众多)不同的整数类型,对于int和float使用两个单独的泛型,对于后者使用协议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 ,可能更喜欢。