我希望编写代码,允许我在不同类型之间构建绑定:
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))
}
我该怎么做,和/或有更好的解决方案吗?
提前致谢。
答案 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
,直到你有一个非常好的和明确的用例。如果你有一个不同的例子,看看会很有趣,但我希望答案是相似的。小心过度延伸强制。