extend generic Array to adopt protocol

后端 未结 3 1989
生来不讨喜
生来不讨喜 2020-12-18 08:35

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

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


        
相关标签:
3条回答
  • 2020-12-18 09:17

    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:

    • Extending typed array by conforming to a protocol in Swift 2

    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.

    0 讨论(0)
  • 2020-12-18 09:17

    Foreword: As @difri correctly mentions in the comment we can not yet create an extension conforming to a protocol when using generic constrains at the same time. There are already a couple of radars open - searching for "extension of type with constraints cannot have an inheritance clause" will yield a couple of them.

    Actual Answer: Building on @LeoDabus awesome answer and my experimenting I came up with the following:

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

    Testing it with

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

    Somewhat correctly prints

    16

    To get the correct answer (at least what I would suspect) you have to wrap the distance into sqrt:

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

    Which then correctly prints

    4

    0 讨论(0)
  • 2020-12-18 09:20

    You can extend SequenceType instead of Array

    extension SequenceType where Generator.Element == Float {
    //
    }
    
    0 讨论(0)
提交回复
热议问题