Swift:协议:捕获类型之间的兼容性

时间:2014-09-16 16:47:53

标签: ios xcode swift

我希望编写代码,允许我在不同类型之间构建绑定:

Map.add(1.0).to(CGPointZero) // (x:1.0, y:1.0)

这是一个人为的例子,但如果能够奏效,可以成为捕捉关系的好方法。在C ++中,这将是直截了当的,你可以创建这样的东西:

class Binding<typename FromType> {

    FromType from;

    to<typename ToType>(ToType toType) {
        return from + toType
    }
}

class Map {

     Binding<T> add<T>(t:T) {
        return Binding<T>(t:t)
    }
}

并且编译器会确定是否可以添加这些类型。

但是,斯威夫特是另一种动物,你需要在类型之前用协议来捕捉这种关系。它可能看起来像这样:

public protocol ScalarArithmetic {
    typealias SomeScalarType

     func +(lhs: SomeScalarType, rhs: Self) -> Self
}

//  Double + CGPoint
public func + (lhs:Double, rhs:CGPoint) -> CGPoint {
    return CGPoint(x: rhs.x + CGFloat(lhs), y:rhs.y + CGFloat(lhs))
}

//  Float + CGPoint
public func + (lhs:Float, rhs:CGPoint) -> CGPoint {
    return CGPoint(x: rhs.x + CGFloat(lhs), y:rhs.y + CGFloat(lhs))
}


class Binding<T> {
    var from: T

    init(t:T) { self.from = t }

    func to<S:ScalarArithmetic where S.SomeScalarType == T>(s:S) -> S {
        return self.from + s
    }
}

class Map {

    class func add<T>(t:T) -> Binding<T> {
        return Binding<T>(t:t)
    }
}

的问题不过,是你如何绑定类型 - 让我们使用CGPoint为一个例子,与多条标量类型的,因此,上述机器可用于一系列的绑定的(如上面的C ++样品)

这不起作用,因为编译器不喜欢重复的语句:

extension CGPoint : ScalarArithmetic {
    typealias SomeScalarType = Double

}

extension CGPoint : ScalarArithmetic {
    typealias SomeScalarType = Float
}

我尝试捕获标量类型,就像它自己的协议一样 - 也许就是这样做的方式,但它将问题推到了一点:

public protocol SomeScalarType {}
extension Double: SomeScalarType {}
extension Float: SomeScalarType {}

public protocol ScalarArithmetic {
    func +(lhs: SomeScalarType, rhs: Self) -> Self
}

//  Double + CGPoint
public func + (lhs:Double, rhs:CGPoint) -> CGPoint {
    return CGPoint(x: rhs.x + CGFloat(lhs), y:rhs.y + CGFloat(lhs))
}

//  Float + CGPoint
public func + (lhs:Float, rhs:CGPoint) -> CGPoint {
    return CGPoint(x: rhs.x + CGFloat(lhs), y:rhs.y + CGFloat(lhs))
}

extension CGPoint: ScalarArithmetic {} // does not conform to ScalarArithmetic
// since it is not implemented in terms of the ScalarArithmetic protocol,
// despite having the necessary operators.

因此,为了让自己回答我自己的答案,这是否是唯一的选择,使ScalarType“可转换为足够”,可以将其转换为所需的原始类型:

public func + (lhs:SomeScalarType, rhs:SomeScalarType) -> CGPoint {
    return CGPoint(x: rhs.x + CGFloat(lhs), y:rhs.y + CGFloat(lhs))
}

我该怎么做,和/或有更好的解决方案吗?

提前致谢。

1 个答案:

答案 0 :(得分:1)

斯威夫特故意不会为你强迫类型。这是一种语言选择,绕过它会很难。您不能+任意数字类型。你的C ++示例是有效的,因为任意数量的东西都可以添加,而它们不能在Swift中。

IMO,您正在尝试做的正确答案正是:

public func + (lhs:Double, rhs:CGPoint) -> CGPoint {
    return CGPoint(x: rhs.x + CGFloat(lhs), y:rhs.y + CGFloat(lhs))
}

完全停止。但附加到+这是一件可怕的事情。你不能有意义地+标量和向量。这种事情更好地定义为:

public func + (lhs:CGPoint, rhs:CGPoint) -> CGPoint {
    return CGPoint(x: rhs.x + lhs.x, y:rhs.y + lhs.y)
}

然后制作你要添加的CGPoint(即使你必须创建一个Diagonal(1)函数来帮助。我知道这是一个人为的例子,但我希望同样如此。完全出问题。制作类型自动强制应该非常小心。创建一个init几乎总是可以让你明确地将一个转换为另一个。然后运算符才有意义。

这对Swift来说是一个强大的类型安全性。例如,我有一些这样的代码:

// Frequency and Time are reciprocols
public func *(lhs: SignalTime,      rhs: SignalFrequency) -> Double { return lhs.seconds * rhs.hertz }
public func *(lhs: SignalFrequency, rhs: SignalTime)      -> Double { return rhs * lhs }

public func /(lhs: Double, rhs: SignalFrequency) -> SignalTime { return SignalTime(seconds: lhs / rhs.hertz) }
public func /(lhs: Double, rhs: SignalTime)      -> SignalFrequency { return SignalFrequency(hertz: lhs / rhs.seconds) }

// Frequency can be scaled by a constant
public func *(lhs: SignalFrequency, rhs: Double)          -> SignalFrequency { return SignalFrequency(hertz: lhs.hertz * rhs) }
public func *(lhs: Double,          rhs: SignalFrequency) -> SignalFrequency { return rhs * lhs }

这一切都非常明确,甚至是单调乏味,但它应该是。有一些有用的方法来组合这些类型,并且有非法的方法来组合这些类型。 x/y返回的完全不同于y/x。这样做已经在编译阶段发现了几个“哦,我偶然将频率乘以频率”错误。试图用最少的代码使所有这些“神奇地工作”倾向于以允许非法操作的方式过度扩展类型。

我会避免ScalarArithmetic,直到你有一个非常好的和明确的用例。如果你有一个不同的例子,看看会很有趣,但我希望答案是相似的。小心过度延伸强制。