【问题标题】:Swift Generics: IntegerType Version Works But Not FloatingPointTypeSwift 泛型:IntegerType 版本有效,但 FloatingPointType 无效
【发布时间】:2016-03-31 05:20:15
【问题描述】:

我使用 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 版本上执行此操作。为什么?

【问题讨论】:

    标签: ios swift macos generics foundation


    【解决方案1】:

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

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

    因此,符合协议FloatingPointType 的通用T 不一定(在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 并向您的“子协议”添加所需的任何其他方法,例如:

    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 作为泛型的类型约束:

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

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

    // 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-initializer、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

    【讨论】:

    • 这似乎是合法的,但我认为理想的答案是 Apple 制作它,以便我尝试做的事情无论是第一个协议还是第二个协议都可以顺利进行。这对我来说似乎很直观。我也在 Cocoa Dev 邮件列表上询问过这个问题。有了你的回答和我从那里得到的信息,我认为这件事已经解决了。谢谢...
    • @PartiallyFrozenOJ 乐于助人!
    • @dfri - 这个解释清楚地涵盖了这个问题的许多方面,以至于我很想把它印在我的手背上。
    • @Zhora 很高兴听到它对您很有帮助!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-26
    • 2019-03-09
    • 1970-01-01
    相关资源
    最近更新 更多