Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extending typed array by conforming to a protocol in Swift 2

I want to extend a typed array Array<SomeType> so that it conforms to a protocol SomeProtocol. Now I know you can extend a typed array like below:

extension Array where Element: SomeType { ... }

And you can also extend an object to conform to a protocol like so:

extension Array: SomeProtocol { ...  }

But I can't figure out what's the right syntax to have the typed array conform to a protocol, something like:

extension (Array where Element: SomeType): SomeProtocol { ... }

Any Swift 2 experts know how to do this?

like image 813
James Hu Avatar asked Dec 26 '15 00:12

James Hu


3 Answers

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"
like image 159
R Menke Avatar answered Nov 13 '22 19:11

R Menke


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)

like image 41
Petter Avatar answered Nov 13 '22 20:11

Petter


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

like image 2
dfrib Avatar answered Nov 13 '22 21:11

dfrib