Swift,数学函数和协议

4

我正在尝试创建两个协议ArithmeticType和MathematicType,它们将用于通用运算符函数的where从句中

protocol ArithmeticType {
    func +(lhs: Self, rhs: Self) -> Self
    func -(lhs: Self, rhs: Self) -> Self
    func *(lhs: Self, rhs: Self) -> Self
    func /(lhs: Self, rhs: Self) -> Self
}

extension Int : ArithmeticType {
}

extension Double : ArithmeticType {
}

extension Float : ArithmeticType {
}

算术类型按预期工作,Int、Float和Double都符合它。但是以下内容失败了

import Darwin

protocol MathematicType {
    func sin(x: Self) -> Self
}

extension Double : MathematicType {
}

extension Float : MathematicType {
}

在游乐场的控制台输出中,我读到:

Playground execution failed: <EXPR>:35:1: error: type 'Double' does not conform to protocol 'MathematicType'
extension Double : MathematicType {
^
<EXPR>:32:10: note: protocol requires function 'sin' with type 'Double -> Self'
    func sin(x: Self) -> Self
         ^
<EXPR>:39:1: error: type 'Float' does not conform to protocol 'MathematicType'
extension Float : MathematicType {
^
<EXPR>:32:10: note: protocol requires function 'sin' with type 'Float -> Self'
    func sin(x: Self) -> Self
         ^

我希望数学函数的行为像上面的运算符���有办法吗?
==编辑:
现在我意识到试图简化我的问题是一个坏主意。这个类的上下文是(可选值的向量)。
class Vector<T> {

    var data=[T?]()

    init(fromArray: Array<T>) {
        for i in fromArray {
            data.append(i)
        }
    }

    init() {
    }

    init(count: Int){
        for i in 0..<count {
            data.append(nil)
        }
    }

    init(count: Int, repeatedValue: T) {
        for i in 0..<count {
            data.append(repeatedValue)
        }
    }

    func count() -> Int {
        return data.count
    }

    func append(newElement: T?) {
        data.append(newElement)
    }

    subscript(index: Int) -> T? {
        let i = index>0 ? index % count() : -index % count()
        return data[i]
    }
}

除了它,我定义了一个适用于+运算符的通用函数

func +<T where T: ArithmeticType>(left: Vector<T>, right: Vector<T>) -> Vector<T> {
    let resultCount = max(left.count(),right.count())
    var result = Vector<T>()
    for i in 0..<resultCount {
        if left[i] != nil && right[i] != nil {
            result.append(left[i]!+right[i]!)
        }
        else {
            result.append(nil)
        }
    }
    return result
}

这个函数按预期工作,但是当我尝试定义一个通用的正弦函数时

func sin<T where T : FloatingPointType>(x: Vector<T>) -> Vector<T>{
    var result = Vector<T>()
    for i in 0..<x.count() {
        if let o = x[i] {
            result.append(sin(o))
        }
        else {
            result.append(nil)
        }
    }
    return result
}

我得到了“找不到接受所提供参数的正弦重载”的错误提示。

之后,我尝试使用MathematicType来模仿我已经为+运算符完成的操作。

(ArithmeticType是从导入Swift的源代码IntegerAritmeticType中获得启发的,而不是我对自己正在做什么的了解)

== 更新

如果我只为Double编写一个专门的函数。

func sin(x: Vector<Double>) -> Vector<Double>{
    var result = Vector<Double>()
    for i in 0..<x.count() {
        if let o = x[i] {
            result.append(Darwin.sin(o))
        }
        else {
            result.append(nil)
        }
    }
    return result
}

它按预期工作。

所以问题可能变成“如何将其推广到Double和Float”?


我认为如果你想让一个类型符合多个协议,你应该这样做:extension Double : MathematicType, ArithmeticType { } - Atomix
你说得对,但是我把它们分开来是为了指出我的问题在哪里。 - Uli
你的 func sin<T where T : FloatingPointType> 示例看起来不错,但是它不能调用标准库的 sin 函数,需要检查和转换其参数类型 -- 请参考我的回答及其新评论。 - rickster
@rickster 我不明白在 func sin<T where T : FloatingPointType> 中检查类型的意义,与其如此,不如有一些专门的 func sin(x: Vector<Double>) -> Vector<Double> 和 Float 的相同函数。在这两种情况下,这对于泛型编程来说是一个强大的限制。我想知道自由函数是否被故意从泛型编程中省略,或者苹果认为它们可能没有用处。 - Uli
3个回答

2
编译器错误是因为您将sin()声明为MathematicType协议的方法,然后声明Double实现了MathematicType,但实际上没有编写sin()方法。
extension Double {
    func sin(x: Double) -> Double {
        return Darwin.sin(x)
    }
}

我认为这不是你想要的,你想要能够编写这样的代码:

let myAngle = 3.14159
let sineValue = myAngle.sin()

如果是这种情况,您的协议和扩展应该像这样:

如果是这种情况,您的协议和扩展应该长成这个样子:

protocol MathematicType {
    func sin() -> Self
}

extension Double : MathematicType {
    func sin() -> Double {
        return Darwin.sin(self)
    }
}

不,我绝对不想要一个方法,相反,我需要使用带有通用类型T的sin函数,该类型可以是Float或Double,请查看我在问题中的编辑。 - Uli

2

您的MathematicType协议以及在FloatDouble扩展中对其进行的一致性声明表明,FloatDouble应该提供sin作为实例方法。也就是说,您表示应该能够编写以下代码:

let zero = 0.0    // inferred type Double
zero.sin(1.5707963268) // returns about 1.0

请注意,调用sinzero的值实际上没有关系,所以这可能不是您想要的行为。
您可能希望将sin作为自由函数,这样您就可以只编写以下内容:
sin(1.5707963268)

好的,那么你的工作已经完成了……标准库已经定义了以下两个:

func sin(x: Double) -> Double
func sin(x: Float) -> Float

如果您希望您的MathematicType可以用作通用参数,即“您可以取正弦的类型”,那么您需要一个通用的sin函数。类似于这样的东西(快速的临时解决方案,可能有更好的解决方案):

func sine<T: FloatingPointType>(x: T) -> T {
    if let v = x as? Double {
        return sin(v) as T
    }
    if let v = x as? Float {
        return sin(v) as T
    }
    fatalError("unknown FloatingPointType")
}

(注意,FloatDouble已经符合了一个协议,所以我在示例中使用了它。)

从您的回答中,我理解在协议大括号内声明“方法”,所以我仍然不明白的是:1)算术类型中的运算符是否为方法?我认为它们是自由函数。2)为什么算术运算符和sin函数不同? - Uli
运算符是协议声明通常规则的例外。您将运算符定义为自由函数(即在全局范围内),但通过在“protocol”中列出它们,使它们成为协议的要求。 - rickster
  1. sin 不是一个运算符,而且只有自由函数可以列为协议的要求。如果你想要一个能够操作多种类型的函数,请编写一个通用函数。如果你想让你的通用函数将其通用参数传递给一个接受特定类型的标准库函数,你就必须检查和转换类型... 就像我回答中的通用 sine 函数一样。
- rickster

1
我认为问题在于操作符要求和实例方法要求是不同的,尽管它们在语法上看起来相似。操作符总是在全局级别定义的,因此操作符要求是对全局级别操作符的要求。另一方面,实例方法要求是对实例方法的要求。没有办法指定全局函数需求。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接