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?
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"
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)
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 :)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With