Swift 泛型:IntegerType 版本有效但 FloatingPointType 无效

Swift Generics: IntegerType Version Works But Not FloatingPointType

我使用 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 版本,编译器表示 "Binary operator '*' cannot be applied to two 'T' operands"。但是,它不会在 IntegerType 版本上执行此操作。为什么?

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

另一方面,IntegerType 协议确实包含操作符方法的蓝图。

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

举个例子,看看下面的协议一些基本类型的扩展以符合它

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

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


无论如何,您可以自己解决这个问题,方法是创建一个新协议,将其添加为 FloatingPointType 函数中泛型 T 的附加类型约束。

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 并向您的 "child protocol" 添加所需的任何其他方法,例如:

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 协议(甚至作为我们自定义协议的父协议)吗?如果我们只想为处理两个 swift 浮点类型 DoubleFloat 创建一个泛型,那么我们不妨只应用协议 MyNecessaryFloatingPointTypeOperations 作为类型约束我们的通用:

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

您可能已经知道,我们需要泛型符合单个蓝图的 FloatingPointType 协议:确定我们的泛型函数可以使用整数初始化器初始化 T 个实例,即init(_ value: Int)。例如,在您的函数中:

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

但是,如果这是我们从 FloatingPointType 协议中使用的唯一蓝图,我们不妨将其作为蓝图添加到我们自己的协议中,并移除对 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
}

有了这个,我们意识到我们真的不需要两个单独的整数类型和浮点类型的泛型。由于我们(以您的示例为例)只需要 1。一个 by-int 初始化器,2.* 二元运算符,和 3.+= 赋值运算符,我们可以为所有符合这些蓝色标记的类型构造一个泛型作为类型约束,并扩展那些我们希望通过该协议涵盖我们的仿制药。

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

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

总结一下:如果您明确知道您希望泛型涵盖哪些类型,您可能会编写自己的自定义协议并扩展这些类型以适应这种情况。如果您想要所有(许多)不同的整数类型,则可能更喜欢对整数和浮点数使用两个单独的泛型,并为后者使用协议 IntegerType