I want to use a generic typed class and a type constraint:
class MyCustomClass<T : Equatable> {
var a: Array<T>
init() {
a = Array<T>()
}
}
That works fine. But what happens if I want to use a second protocol, f.e.
class MyCustomClass<T : Equatable, IndexableBase> {
var a: Array<T>
init() {
a = Array<T>()
}
}
It says that the initializer fails because I have to use 2 instead of 1 argument. That I don't understand.
Swift 3
(I've replaced IndexableBase
with Collection
in your example, you should prefer the latter)
As per Swift 3, protocol composition use the infix operator &
over the previous protocol<...>
construct, as described in the accepted and implemented evolution proposal:
protocol<P1,P2>
syntax with P1 & P2
syntaxHence, using protocol composition you may place the combined type constraint at the T
placeholder in the parameter list:
class MyCustomClass<T: Equatable & Collection> { /* ... */ }
As an alternative to protocol composition, you could also make use of where
clause to join several type (and subtype) constraint. As per the accepted and implemented evolution proposal:
the where
clause has been moved to end of the declaration, in which case your example would read:
class MyCustomClass<T: Equatable> where T: Comparable { /* ... */ }
or, even place the full protocol composition with the where
clause at the end of the declaration
class MyCustomClass<T> where T: Equatable & Comparable { /* ... */ }
What style should we prefer?
The interesting discussion is what we should consider "best practice", as this particular subject is not specified Swift API guidelines. Do we place
Consider the following contrived example, where we have a construct a protocol that we plan to use as a type constraint (Doable
), which in itself holds an associatedtype
to which we may place a type constraint.
protocol Doable {
associatedtype U
}
Using the different methods above, all the following three alternatives are valid, syntactically
// alternative #1
func foo<T: Equatable & Doable>(_ bar: T) -> () where T.U: Comparable { /* ... */ }
// alternative #2
func foo<T: Equatable>(_ bar: T) -> () where T: Doable, T.U: Comparable { /* ... */ }
// alternative #3
func foo<T>(_ bar: T) -> () where T: Equatable & Doable, T.U: Comparable { /* ... */ }
I'll quote Apple dev Joe Groff from the evolution thread that that brought forth SE-0081:
It's a judgment call. It's my feeling that in many cases, a generic parameter is constrained by at least one important protocol or base class that's worth calling out up front, so it's reasonable to allow things like
func foo<C: Collection>(x: C) -> C.Element
without banishing the
Collection
constraint too far from the front of the declaration.
So in the contrived example above, it might be appropriate to use protocol composition for the type constraints that apply directly to the generic placeholder T
, and place these constraints in the parameter list, whereas the subtype constraint of T.U
is placed at the end of the declaration. Namely, alternative #1 above.
you can use this workaround
class MyCustomClass<T: Equatable where T: IndexableBase > {
var a: Array<T>
init() {
a = Array<T>()
}
}
Swift 4:
class MyCustomClass<T: Equatable> where T: Collection {
var a: Array<T>
init() {
a = Array<T>()
}
}
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