Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

More than one protocol in a type constraint

Tags:

generics

swift

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.

like image 276
altralaser Avatar asked Dec 07 '16 13:12

altralaser


2 Answers

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:

  • SE-0095: Replace protocol<P1,P2> syntax with P1 & P2 syntax

Hence, 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:

  • Se-0081: Move where clause to end of declaration

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

  • all (main type) constraints in the parameter list, or
  • all constraints at the end of the declaration, or
  • split up constraints with some in the parameter list and others placed at the end of the declaration?

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.

like image 100
dfrib Avatar answered Oct 30 '22 16:10

dfrib


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>()
    }
}
like image 29
Franco Meloni Avatar answered Oct 30 '22 15:10

Franco Meloni