扩展通用Array <t>以采用协议

时间:2015-12-26 21:57:20

标签: swift generics

我们说我已经定义了这样一个协议:

protocol EuclideanPoint {
    func distance(other: Self) -> Double
    func dimension() -> UInt
}

现在我想扩展[Float][Double]以采用该协议。

但是以下代码:

extension [Float]: EuclideanPoint {
    func distance(other: [Float]) {
        return Double(zip(self, other).map{a, b in pow(a-b,2)}.reduce(0, combine: +))
    }
    func dimension() {
        return UInt(self.count)
    }
}
由于错误

无效

  

错误:必须在非专用通用类型&#39;数组&#39;上声明约束扩展名。具有由&#39;其中&#39;指定的约束。条款

我发现了类似的问题(例如this),但建议的解决方案是使用extension CollectionType where Generator.Element == S { ... },但在此上下文中会导致错误:

  

错误:协议&#39; CollectionType&#39;只能用作通用约束,因为它具有Self或关联类型要求

有没有解决方案?

编辑:

使用建议的解决方案:

protocol DoubleConvertibleType {
    var doubleValue: Double { get }
}

extension Double : DoubleConvertibleType { var doubleValue: Double { return self         } }
extension Float  : DoubleConvertibleType { var doubleValue: Double { return Double(self) } }
extension CGFloat: DoubleConvertibleType { var doubleValue: Double { return Double(self) } }

extension Array where Element : DoubleConvertibleType {
    func distance(other: Array) -> Double {
        return Double(zip(self, other).map{ pow($0.0.doubleValue - $0.1.doubleValue, 2) }.reduce(0, combine: +))
    }

    func dimension() -> UInt {
        return UInt(self.count)
    }
}

提供[Double][Float] .distance().dimension()方法。但[Double][Float]不能用来代替符合EuclideanPoint协议所需的内容,从而产生错误:

  

错误:键入&#39; [Double]&#39;不符合协议&#39; EuclideanPoint&#39;

3 个答案:

答案 0 :(得分:1)

EDITED

以下解决方案有些通用,符合协议EuclidianPoint,并基于两个假设:

  • 我们允许在distance协议中为方法EuclideanPoint的蓝图包含泛型类型约束,而不是参数类型为{{1} },我们将使用通用(Self)。但是,我们将确定(在编译时)[T][T]的类型相同(此处为Self Self[Double][Float]类型),确定[T]符合协议[Int]

  • 您确定我们将EuclidianPoint.map等函数式编程技术排除在此特定应用程序之外,并专注于实现&#34 ; euclidian协议采用的通用数组&#34; 。 Swift中的这些.reduce.map等功能确实很整洁有用,但在许多应用程序中只是for-hood-for循环的包装器,所以你不会失去任何性能以手工命令式的方式做事。实际上,已知.reduce由于重复的数组复制分配而执行非常不可选的,同时减少了数组(我不会在这里更多地进入此...)。无论如何,也许你可以利用我的例子并将其调整回更具功能性的范例。

我们从一个自定义类型协议.reduce开始,它将作为我们想要包含在我们的泛型中的类型的接口。我们还添加了略微更新的MyTypes协议,我们使用协议EuiclidianPoint作为MyTypes函数蓝图中使用的通用T的类型约束。

distance (...)

请注意,我已将/* Used as type constraint for Generator.Element */ protocol MyTypes { func -(lhs: Self, rhs: Self) -> Self func +=(inout lhs: Self, rhs: Self) } extension Int : MyTypes { } extension Double : MyTypes { } extension Float : MyTypes { } /* Extend with the types you wish to be covered by the generic ... */ /* Used as extension to Array : blueprints for extension method to Array where Generator.Element are constrainted to MyTypes */ protocol EuclideanPoint { func distance<T: MyTypes> (other: [T]) -> Double? func dimension() -> UInt } Double的回复更改为可选;您可以按照自己的意愿处理,但如果distanceself数组的长度不同,或类型otherSelf不同,则需要显示不符合 - 我会在此处使用[T]

我们现在可以通过nil协议实现我们对Array的扩展:

EuclidianPoint

请注意,在/* Array extension by EuclideanPoint protocol */ extension Array : EuclideanPoint { func distance<T: MyTypes> (other: [T]) -> Double? { /* [T] is Self? proceed, otherwise return nil */ if let a = self.first { if a is T && self.count == other.count { var mySum: Double = 0.0 for (i, sElement) in self.enumerate() { mySum += pow(((sElement as! T) - other[i]) as! Double, 2) } return sqrt(mySum) } } return nil } func dimension() -> UInt { return UInt(self.count) } } 函数的内部if子句中,我们使用显式向下转换为distance,但因为我们断言T的元素属于类型Self,这没关系。

无论如何,有了这个,我们就完成了,我们可以测试我们的&#34;泛型&#34;我们现在注意到的数组扩展也符合您的协议T

EuclidianPoint

确定!

我的第一个答案仍然留有一条说明:

将泛型类型数组扩展到协议实际上最近才在这里提出:

一致意见是你无法在#34;整洁的swifty&#34;中执行数组到协议的通用扩展。你可能期望的方式。然而,有一些模拟这种行为的解决方法,一种是我上面使用的行为。如果您对其他方法感兴趣,我建议您查看此主题。

答案 1 :(得分:1)

您可以展开SequenceType而不是Array

extension SequenceType where Generator.Element == Float {
//
}

答案 2 :(得分:1)

前言:正如@difri在评论中正确提到的那样,在同时使用通用约束时,我们还无法创建符合协议的扩展。已经有几个雷达打开 - 搜索“带有约束的类型扩展不能有继承子句”会产生几个雷达。

实际答案:在@LeoDabus awesome answer的基础上进行实验,我想出了以下内容:

protocol DoubleConvertibleType {
    var doubleValue: Double { get }
}

extension Double : DoubleConvertibleType { var doubleValue: Double { return self         } }
extension Float  : DoubleConvertibleType { var doubleValue: Double { return Double(self) } }
extension CGFloat: DoubleConvertibleType { var doubleValue: Double { return Double(self) } }

extension Array where Element : DoubleConvertibleType {
    func distance(other: Array) -> Double {
        return Double(zip(self, other).map{ pow($0.0.doubleValue - $0.1.doubleValue, 2) }.reduce(0, combine: +))
    }

    func dimension() -> UInt {
        return UInt(self.count)
    }
}

使用

进行测试
let arr1 = [1.5, 2, 3]
let arr2 = [5.5, 2, 3]
let arrD = arr1.distance(arr2)

有些正确打印

  

16

要获得正确的答案(至少我怀疑),你必须将distance包裹到sqrt

return sqrt(Double(zip(self, other).map{ pow($0.0.doubleValue - $0.1.doubleValue,2) }.reduce(0, combine: +)))

然后正确打印

  

4