Extend Generic Array<T> to Adopt Protocol

extend generic Array T to adopt protocol

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.

extend generic Array T to adopt protocol

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.

Extending typed array by conforming to a protocol in Swift 2

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"

How to extend protocol Optional, where Wrapped item is Array of Equatable generic elements?

Cristik provided a good solution. An alternative is to write an extension to an optional collection of equatable elements:

extension Optional where Wrapped: Collection, Wrapped.Element: Equatable {
func foo() { }
}

This would be applicable to arrays, array slices, and other collections.

Depending on what the extension does, you may want to use a combination of Collection, MutableCollection, BidirectionalCollection, RandomAccessCollection.

Swift: Extension on [ SomeType T ?] to produce [ T ?] possible?

I don't know if there is a simpler solution now, but you can use the same “trick” as in How can I write a function that will unwrap a generic property in swift assuming it is an optional type? and Creating an extension to filter nils from an Array in Swift, the idea goes back to this Apple Forum Thread.

First define a protocol to which all optionals conform:

protocol OptionalType {
associatedtype Wrapped
var asOptional: Wrapped? { get }
}

extension Optional : OptionalType {
var asOptional: Wrapped? {
return self
}
}

Now the desired extension can be defined as

extension Collection where Element: OptionalType, Element.Wrapped: SomeTypeProtocol {
var values: [Element.Wrapped.NumberType?] {
return self.map( { $0.asOptional?.value })
}
}

and that works as expected:

let arr = [SomeType(value: 123), nil, SomeType(value: 456)]
let v = arr.values

print(v) // [Optional(123), Optional(456)]
print(type(of: v)) // Array<Optional<Int>>

Extend Array to conform to protocol if Element conforms to given protocol

Swift 4.2

In Swift 4.2 I was able to extend an array with the elements conforming to a protocol like this:

public extension Array where Element: CustomStringConvertible{
public var customDescription: String{
var description = ""
for element in self{
description += element.description + "\n"
}

return description
}
}

How to make a generic class conform to a protocol for specific type?

No, such construct isn't possible (circa Swift 3.1 at least).

For instance:

class SomeClass { }
protocol SomeProtocol { }

extension Matrix: SomeProtocol where T == SomeClass { }

Gives a very clear error message:

Extension of type Matrix with constraints cannot have an inheritance clause.


But all is not lost... as correctly noted by Alexander, there is already a proposal lined up for Swift 4! The feature will be called Conditional Conformances (SE-0143).

A nice example for all protocol-oriented programming hackers out there:

extension Array: Equatable where Element: Equatable {
...
}

If an array contains equatable elements than said array is also equatable.


Update. Swift 4 is out but this feature hasn’t landed yet. We may need to wait until Swift 5 for this...

How would I use an array of generics with a type parameter conforming to a protocol?

To see what's wrong with your scenario, forget about trying to declare the type of this array, and try to actually make such an array out of actual objects:

protocol MyProtocol {}
struct MyStruct<T: MyProtocol> {
let myProp: T
}
struct S1 : MyProtocol {}
struct S2 : MyProtocol {}
let myStruct1 = MyStruct(myProp: S1())
let myStruct2 = MyStruct(myProp: S2())
let array = [myStruct1, myStruct2] // error

The compiler kicks back: "heterogeneous collection literal could only be inferred to '[Any]'". And that sums it up. The types of myStruct1 and myStruct2 have nothing in common, so you cannot make an array of them.

That is why you are not able to declare the array to be of a type that will embrace both of them. There is no such type. The types of myStruct1 and myStruct2, namely MyStruct<S1> and MyStruct<S2>, are unrelated.

I know that they look related, because the word "MyProtocol" in the original declaration appears to provide some sort commonality. But the word "MyProtocol" does not designate a type; it designates a constraint on the actual type, saying that whatever this one type is, it must be an adopter of MyProtocol. S1 and S2 are two different types, and so MyStruct<S1> and MyStruct<S2> are two different types. You can't put them together in an array. The fact that both S1 and S2 happen to adopt MyProtocol is irrelevant.

Part of the difficulty may be that you think that two generic types are somehow related because their parameterized types are related. That is not the case. The classic example is a class and its subclass:

class Cat {}
class Kitten: Cat {}
struct Animal<T: Cat> {}
let cat = Animal<Cat>()
let kitten = Animal<Kitten>()
let array2 = [cat, kitten] // error

We get the same compile error. Again, you might imagine that you can put cat and kitten together in an array because Kitten is a subclass of Cat. But that is not true. Animal<Cat> and Animal<Kitten> are unrelated types.

Extension on Array where element is a generic struct in Swift

You will need to create a protocol with an associated type:

protocol MyGenericStructProtocol {
associatedtype GenericParameter
}

Let your struct adopt the protocol, either directly, or using an extension:

extension MyStruct: MyGenericStructProtocol {
typealias GenericParameter = T
}

Now you can reference the generic type inside your extension of Array:

extension Array where Element: MyGenericStructProtocol {
func doWork() -> [Element.GenericParameter] {
return []
}
}

Check out a fully working example on this GitHub gist



Related Topics



Leave a reply



Submit