Extending typed array by conforming to a protocol in Swift 2

后端 未结 3 1744
生来不讨喜
生来不讨喜 2021-01-13 04:48

I want to extend a typed array Array so that it conforms to a protocol SomeProtocol. Now I know you can extend a typed array like b

3条回答
  •  太阳男子
    2021-01-13 05:29

    I've been fiddling around some with this myself, and there are some ways to somewhat mimic the behaviour you're looking for.


    Approach #1

    Define a protocol SomeType to act as the type constraint for the types that you wish to be covered by your Array extension for SomeProtocol; where the latter contains blueprints for some neat methods that you wish to extend Array.

    protocol SomeType {
        var intValue: Int { get }
        init(_ value: Int)
        func *(lhs: Self, rhs: Self) -> Self
        func +=(inout lhs: Self, rhs: Self)
    }
    
    extension Int : SomeType { var intValue: Int { return self } }
    extension Double : SomeType { var intValue: Int { return Int(self) } }
        /* Let's not extend 'Float' for now
    extension Float : MyTypes { var intValue: Int { return Int(self) } } */
    
    protocol SomeProtocol {
        func foo(a: [T]) -> Int?
    }
    

    Now, you can extend Array to SomeProtocol, and by using the is keyword, you can assert that the you generic T (constrained by SomeType) and elements of Self are of the same type by using the is keyword, that, if true, is followed by explicit casting:

    extension Array : SomeProtocol {
        func foo(a: [T]) -> Int? {
            /* [T] is Self? proceed, otherwise return nil */
            if let b = self.first {
                if b is T && self.count == a.count {
                    var myMultSum: T = T(0)
    
                    for (i, sElem) in self.enumerate() {
                        myMultSum += (sElem as! T) * a[i]
                    }
                    return myMultSum.intValue
                }
            }
            return nil
        }
    }
    

    We've now extended Array with elements of SomeType with the function foo(...) blueprinted in the protocol SomeProtocol.

    /* Tests */
    let arr1d : [Double] = [1.0, 2.0, 3.0]
    let arr2d : [Double] = [-3.0, -2.0, 1.0]
    
    let arr1f : [Float] = [1.0, 2.0, 3.0]
    let arr2f : [Float] = [-3.0, -2.0, 1.0]
    
    func bar (arr1: [U], _ arr2: [U]) -> Int? {
        return arr1.foo(arr2)
    }
    let myInt1d = bar(arr1d, arr2d) // -4, OK
    
    let myInt1f = bar(arr1f, arr2f)
        /* Compile time error: "Cannot convert value of type '[Float]'
           to expected argument type '[_]'"                            */
    

    Ok! We expected the final compile time error here as 'Float' does not conform to SomeType protocol.


    Approach #2

    Now for another approach: I've based the generics that follow on this excellent post by Milen Dzhumerov, here adapted for an array and with some different extension method examples.

    For this example, we're implementing a "generic" protocol extension for Array:s of type Double or Float, represented by the type constraint protocol SomeType

    protocol SomeType {
        init(_ value: Int)
        init(_ value: Double)
        init(_ value: Float)
        func == (lhs: Self, rhs: Self) -> Bool
    }
    
    extension Double: SomeType {}
    extension Float: SomeType {}
    
    protocol GenericProtocol {
        typealias AbstractType : SequenceType
        func repeatNumberNumberManyTimes(arg: Int) -> AbstractType
        func removeRandomElement(arg: AbstractType) -> AbstractType
        func countNumberOf42s(arg: AbstractType) -> Int
    
    }
    

    Forward the GenericProtocol to AbstractType (which, here, conforms to SequenceType) using a structure, and implement the protocol blueprints in the latter:

    struct SomeArrayProtocol : GenericProtocol {
        private let _repeatNumberNumberManyTimes : (Int) -> T
        private let _removeRandomElement : (T) -> T
        private let _countNumberOf42s : (T) -> Int
    
        init

    (_ dep : P) { _repeatNumberNumberManyTimes = dep.repeatNumberNumberManyTimes _removeRandomElement = dep.removeRandomElement _countNumberOf42s = dep.countNumberOf42s } func repeatNumberNumberManyTimes(arg: Int) -> T { return _repeatNumberNumberManyTimes(arg) } func removeRandomElement(arg: T) -> T { return _removeRandomElement(arg) } func countNumberOf42s(arg: T) -> Int { return _countNumberOf42s(arg) } }

    Implement the actual methods for the GenericProtocol blueprints in another structure, where the generic is now constrained to SomeType type constraint (protocol). Note that it's this part that mimics your wished fir (but straightforwardly unattainable) form extension (Array where Element: SomeType): SomeProtocol { ... }:

    struct SomeArrayGenericExtensions : GenericProtocol {
        typealias AbstractType = Array
        func repeatNumberNumberManyTimes(arg: Int) -> [T] {
            return Array(count: arg, repeatedValue: T(arg))
        }
        func removeRandomElement(arg: [T]) -> [T] {
            var output = [T]()
            let randElemRemoved = Int(arc4random_uniform(UInt32(arg.count-1)))
            for (i,element) in arg.enumerate() {
                if i != randElemRemoved {
                    output.append(element)
                }
            }
            return output
        }
        func countNumberOf42s(arg: [T]) -> Int {
            var output = 0
            for element in arg {
                if element == T(42) {
                    output++
                }
            }
            return output
        }
    }
    

    Finally, some tests:

    let myGenericExtensionUsedForDouble : SomeArrayProtocol> = SomeArrayProtocol(SomeArrayGenericExtensions())
    let myGenericExtensionUsedForFloat : SomeArrayProtocol> = SomeArrayProtocol(SomeArrayGenericExtensions())
    // let myGenericExtensionUsedForInt : SomeArrayProtocol> = SomeArrayProtocol(SomeArrayGenericExtensions()) // Error! Int not SomeType, OK!
    
    var myDoubleArr = [10.1, 42, 15.8, 42.0, 88.3]
    let my10EntriesOfTenDoubleArr = myGenericExtensionUsedForDouble.repeatNumberNumberManyTimes(10) // ten 10:s
    let myFloatArr : Array = [1.3, 5, 8.8, 13.0, 28, 42.0, 42.002]
    let myIntArr = [1, 2, 3]
    
    let a = myGenericExtensionUsedForDouble.countNumberOf42s(myDoubleArr) // 2
    let b = myGenericExtensionUsedForFloat.countNumberOf42s(myFloatArr) // 1
    
    myDoubleArr = myGenericExtensionUsedForDouble.removeRandomElement(myDoubleArr) // [10.1, 15.8, 42.0, 88.3]
    

    I somewhat unsure whether Approach 2 above actually has some practical applications for arrays or not (in Milans coverage he treats non-sequence types, perhaps more useful); it's quite a bit of work for not so much extra bang for the buck. However, it can be an instructive, and quite entertaining exercise :)

提交回复
热议问题