Extending typed array by conforming to a protocol in Swift 2

后端 未结 3 1745
生来不讨喜
生来不讨喜 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:28

    You can't apply a lot of logic to conformance. It either does or does not conform. You can however apply a little bit of logic to extensions. The code below makes it easy to set specific implementations of conformance. Which is the important part.

    This is used as a typed constraint later.

    class SomeType { }
    

    This is your protocol

    protocol SomeProtocol {
    
        func foo()
    
    }
    

    This is an extension of the protocol. The implementation of foo() in an extension of SomeProtocol creates a default.

    extension SomeProtocol {
    
        func foo() {
            print("general")
        }
    }
    

    Now Array conforms to SomeProtocol using the default implementation of foo(). All arrays will now have foo() as a method, which is not super elegant. But it doesn't do anything, so it's not hurting anyone.

    extension Array : SomeProtocol {}
    

    Now the cool stuff: If we create an extension to Array with a type constraint for Element we can override the default implementation of foo()

    extension Array where Element : SomeType {
        func foo() {
            print("specific")
        }
    }
    

    Tests:

    let arrayOfInt = [1,2,3]
    arrayOfInt.foo() // prints "general"
    
    let arrayOfSome = [SomeType()]
    arrayOfSome.foo() // prints "specific"
    
    0 讨论(0)
  • 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<SomeType> 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<T: SomeType>(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<T: SomeType>(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<U: SomeType> (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<T: SequenceType> : GenericProtocol {
        private let _repeatNumberNumberManyTimes : (Int) -> T
        private let _removeRandomElement : (T) -> T
        private let _countNumberOf42s : (T) -> Int
    
        init<P : GenericProtocol where P.AbstractType == T>(_ 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<T: SomeType> : GenericProtocol {
        typealias AbstractType = Array<T>
        func repeatNumberNumberManyTimes(arg: Int) -> [T] {
            return Array<T>(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<Array<Double>> = SomeArrayProtocol(SomeArrayGenericExtensions())
    let myGenericExtensionUsedForFloat : SomeArrayProtocol<Array<Float>> = SomeArrayProtocol(SomeArrayGenericExtensions())
    // let myGenericExtensionUsedForInt : SomeArrayProtocol<Array<Int>> = 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<Float> = [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 :)

    0 讨论(0)
  • 2021-01-13 05:37

    In more recent versions of Swift it is possible to write:

    extension Array: SomeProtocol where Element == SomeType { ... }

    Unsure in which version of Swift this became possible, but the following works in Swift 4.1

    class SomeType { }
    
    protocol SomeProtocol {
        func foo()
    }
    
    extension Array: SomeProtocol where Element == SomeType {
        func foo() {
            print("foo")
        }
    }
    
    let arrayOfSome = [SomeType()]
    arrayOfSome.foo() // prints "foo"
    
    let arrayOfInt = [1,2,3]
    arrayOfInt.foo() // Will not compile: '[Int]' is not convertible to 'Array<SomeType>'
    

    (I am aware that the question specifically asks for Swift 2, but I am adding this for reference)

    0 讨论(0)
提交回复
热议问题