扩展通用数组< T>采用协议 [英] extend generic Array<T> to adopt protocol

查看:99
本文介绍了扩展通用数组< T>采用协议的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设我已经定义了这样一个协议:

pre $协议EuclideanPoint {
func distance(other:Self ) - > Double
func dimension() - > UInt
}

现在我想扩展 [Float ] [Double] 来采用该协议。 但是下面的代码:

 扩展名[Float]:EuclideanPoint {
func distance(other:[Float]){
return double(zip(self,other).map {a,b in pow(ab,2)}。reduce(0,combine:+))
}
func dimension(){
返回UInt(self.count)
}
}

无效,因为错误:
$ b


错误:约束扩展必须在非特定泛型类型'Array'上声明,约束由'where'子句指定 p>

我发现了类似的问题(如这个),但是建议的解决方案是使用扩展名CollectionType,其中Generato r.Element == S {...} ,但在这种情况下会导致错误:


error:protocol'CollectionType'只能用作通用约束,因为它具有自我或相关类型的要求


有没有解决方案到此?

编辑:

使用建议的解决方案:

  protocol DoubleConvertibleType {
var doubleValue:Double {get}
}

扩展Double:DoubleConvertibleType {var doubleValue:Double {return self}}
扩展名浮点数:DoubleConvertibleType {var doubleValue:Double {return Double(self)}}
扩展名CGFloat:DoubleConvertibleType {var doubleValue:Double {return Double(self)}}

扩展数组元素: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协议,产生错误:
$ b


error:type'[Double]'不符合协议'EuclideanPoint'$ / b $ b


解决方案

EDITED






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


  • 我们可以在您的 EuclideanPoint 中为方法距离的蓝图包含泛型约束协议,而不是参数类型是 Self ,我们将使用通用的( [T] )。然而,我们将确定(在编译时) [T] Self (和在这里, [Double] [Float] [Int] type),并确认[T]符合协议 EuclidianPoint

  • code> .reduce 出于这个特定的应用程序,并且只关注获得一个采用euclidian协议的通用数组 。在Swift中这些 .map .reduce 等特性确实很好用,但在许多应用程序中只是封装在引擎盖后面的循环中,所以你不会因为手动命令式的风格而失去任何性能。事实上, .reduce 由于在减少数组的同时重复数组拷贝分配而被认为是非常不可选的(我在这里不会详细讨论这个.. )。无论如何,也许你可以利用我的例子,并将其调整回更实用的范例。

  • $ b

    我们从一个自定义类型协议开始, MyTypes ,它将作为我们希望包含在泛型中的类型的接口。我们还添加了稍微更新的 EuiclidianPoint 协议,我们在协议中使用协议 MyTypes 作为类型限制到通用 T 用于距离(...)函数的蓝图。

      / *用作Generator.Element的类型约束* / 
    协议MyTypes {
    func - (lhs:Self,rhs:Self) - > Self
    func + =(inout lhs:Self,rhs:Self)
    }

    扩展名Int:MyTypes {}
    扩展名Double:MyTypes {}
    扩展名Float:MyTypes {}
    / *扩展您希望覆盖的类型... * /

    / *用作数组的扩展名:用于扩展方法的蓝图
    将Array.Element约束为MyTypes的数组* * /
    协议EuclideanPoint {
    func distance< T:MyTypes> (其他:[T]) - >双?
    func dimension() - > UInt
    }

    请注意,我更改了 Double 返回距离为可选;你可以像你一样处理,但如果 self 和其他数组的长度不同,或者类型 Self [T] 不同,需要显示不符合项 - 我将使用 nil 在这里。

    现在我们可以通过 EuclidianPoint 来实现我们对 Array 的扩展$ c $>协议:

    $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $
    $ b func距离< T:MyTypes> (其他:[T]) - >双? {
    / * [T]是自我吗?继续,否则返回nil * /
    如果让a = self.first {
    如果a是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)
    }
    }



    <请注意,在距离函数的inner if子句中,我们使用明确的向下转换为 T ,但是我们断言 Self 的元素的类型是 T ,这是可以的。



    总之,我们完成了,我们可以测试我们的通用数组扩展,现在我们注意到它也符合您的协议 EuclidianPoint

      / *测试和示例* / 
    let arr1d:[Double] = [3.0,4.0, 0.0]
    let arr2d:[Double] = [-3.0,-4.0,0.0]
    let arr3d:[Double] = [-3.0,-4.0]

    let arr1f :[float] = [-3.0,-4.0,0.0]

    let arr1i = [1,2,3]

    let _a = arr1d.dimension()// 3,OK
    let _b = arr1d.distance(arr2d)// 10,OK(A-> B dist)
    let _c = arr1d.distance(arr1f)// nil(Incomp。types)
    let _d = arr1d.distance(arr3d)// nil(Incomp size)
    let _e = arr1i.distance(arr1d)// nil(Incomp。类型)

    / *用于函数调用:限制为
    的通用数组参数符合协议'EuclidianPoint'的通用数组参数* /
    func bar< T:MyTypes ,U:协议< EuclideanPoint,_ArrayType> U.Generator.Element == T> (arr1:U,_arr2:U) - >双? {

    // ...

    return arr1.distance(Array(arr2))
    / *我们需要明确地告诉距离函数
    在这里我们发送一个数组,通过使用Array(..)初始化器* /

    初始化一个
    数组let myDist = bar(arr1d,arr2d)// 10 ,OK

    好!




    我的第一个答案仍然存在:

    通用类型数组扩展到协议实际上是最近在这里问的:





    共识是你无法对数据进行通用的扩展,以一种你可能期望的整洁快捷的方式进行协议。但是,有一些解决方法可以模仿这种行为,其中之一就是我上面使用的行为。如果你对另一种方法感兴趣,我建议你看看这个线程。


    Let's say I've defined such a protocol:

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

    Now I'd like to extend [Float] and [Double] to adopt that protocol.

    But the following code:

    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)
        }
    }
    

    is invalid because of the error

    error: constrained extension must be declared on the unspecialized generic type 'Array' with constraints specified by a 'where' clause

    I found similar questions (like this), but the suggested solution is to use extension CollectionType where Generator.Element == S { ... }, but in this context it leads to the error:

    error: protocol 'CollectionType' can only be used as a generic constraint because it has Self or associated type requirements

    Is there any solution to this?

    EDIT:

    using the proposed solution:

    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)
        }
    }
    

    gives [Double] and [Float] the .distance() and .dimension() methods. Yet [Double] or [Float] cannot be used in place of something that is required to conform to the EuclideanPoint protocol, producing the error:

    error: type '[Double]' does not conform to protocol 'EuclideanPoint'

    解决方案

    EDITED


    The following solution is somewhat generic, conforms to protocol EuclidianPoint, and is based upon two assumptions:

    • That we're allowed to include a generic type constraint for your blueprint for method distance in your EuclideanPoint protocol, and that, instead of argument type being Self, we'll use a generic ([T]). We will, however, ascertain (at compile time) that [T] is of the same type as Self (and here, Self of [Double], [Float] or [Int] type), and ascertain that [T] conforms to the protocol EuclidianPoint.

    • That you're ok that we leave functional programming techniques such as .map and .reduce out of this specific application, and focus only on attaining a "generic array adopted to euclidian protocol". These .map, .reduce etc feats in Swift are indeed neat and useful, but are in many applications just wrappers for behind-the-hood for-loops, so you won't lose any performance over doing things in a manual imperative style. In fact, .reduce is known to perform quite non-optional due to repeated array-copy-assignments while reducing the array (I won't go into this more here...). Anyway, perhaps you can make use of my example and tweak it back to something more functional-paradigmy.


    We begin by a custom type protocol, MyTypes, that will act as an interface for which types we want to include in our generic. We also add the slightly updated EuiclidianPoint protocol, where we use protocol MyTypes as a type restraint to the generic T used in the distance (...) function blue-print.

    /* 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
    }
    

    Note that I've changed the Double return of distance to an optional; you may handle this as you will, but in case the lengths of self and other arrays differ, or types Self and [T] differ, there will be some need of showing non-conformance -- I'll use nil for this here.

    We can now implement implement our extension of Array by the EuclidianPoint protocol:

    /* 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)
        }
    }
    

    Note that in the inner if clause of the distance function we use an explicit down cast to T, but since we've asserted that elements of Self are of type T, this is ok.

    Anyway, with this, we're done, and we can test our "generic" array extensions, which we note now also conforms to your protocol EuclidianPoint.

    /* Tests and Examples */
    let arr1d : [Double] = [3.0, 4.0, 0.0]
    let arr2d : [Double] = [-3.0, -4.0, 0.0]
    let arr3d : [Double] = [-3.0, -4.0]
    
    let arr1f : [Float] = [-3.0, -4.0, 0.0]
    
    let arr1i = [1, 2, 3]
    
    let _a = arr1d.dimension() // 3, OK
    let _b = arr1d.distance(arr2d) // 10, OK (A->B dist)
    let _c = arr1d.distance(arr1f) // nil (Incomp. types)
    let _d = arr1d.distance(arr3d) // nil (Incomp. sizes)
    let _e = arr1i.distance(arr1d) // nil (Incomp. types)
    
        /* for use in function calls: generic array parameters constrained to
           those that conform to protocol 'EuclidianPoint', as requested     */
    func bar<T: MyTypes, U: protocol<EuclideanPoint, _ArrayType> where U.Generator.Element == T> (arr1: U, _ arr2: U) -> Double? {
    
        // ...
    
        return arr1.distance(Array(arr2))
            /* We'll need to explicitly tell the distance function
               here that we're sending an array, by initializing an 
               array using the Array(..) initializer                */
    }
    let myDist = bar(arr1d, arr2d) // 10, OK
    

    Ok!


    A note still remaining from my first answer:

    Extension of generic type Array to protocol was actually just recently asked here:

    The consensus is the you cannot do a generic extension of array to a protocol in the "neat swifty" way that you possible expect. There are however workarounds to mimic such a behaviour, one being the one I've used above. If you are interested in another method, I suggest you look into this thread.

    这篇关于扩展通用数组&lt; T&gt;采用协议的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆