[swift-evolution] Proposal for generic protocols

Kevin Ballard kevin at sb.org
Thu Dec 3 16:57:09 CST 2015


Changing all typealiases in protocols to generic arguments is a pretty
significant change to the language, with a very large impact on existing
code. It also opens the door to implementing the same protocol with
different types, which is something that Swift does not currently allow.

I think Rust's trait system is a good example of the right way to do
this. Rust traits are like Swift protocols, but they started out with
only generics, no associated types. Later on they gained associated
types as well (with the same limitation that Swift protocols have,
where any trait with an associated type cannot be used as a "trait
object"). The end result is Rust traits can have both generics and
associated types, and the choice of which to use depends on what it's
for. Also, a type in Rust can implement the same trait with different
generic parameters (but for any parameterized trait, it can only have
one implementation regardless of associated types). This is also how
Rust has implemented multi-dispatch (Rust does not have method
overloading in general). And the way you're supposed to think about
this is generic type parameters to a trait are "input types", and
associated types are "output types". So any given type can implement
the same protocol as many times as it wants with distinct input types,
but for every set of input types, there is only one set of output
types. And this works very well.

An example of how this is used is the trait that powers the + operator,
called std::ops::Add. It's defined as

pub trait Add<RHS = Self> {    type Output;    fn add[1](self, rhs: RHS)
-> Self::Output[2]; }

(the `= Self` bit is a defaulted type parameter)

This means that for any given type T, it can support addition with any
number of other types, but for every pair of types (T,U), the expression
`T + U` can only ever have one return value. To demonstrate how this
would work in Swift, you can imagine supporting `+` with NSNumber
against different numeric types:

extension NSNumber: Add<Int> {    typealias Output = Int    func
add(rhs: Int) -> Int {        return integerValue + rhs    } }

extension NSNumber: Add<UInt> {    typealias Output = UInt    func
add(rhs: UInt) -> UInt {        return unsignedIntegerValue + rhs    } }

// etc...

Besides the clean distinction between "input" and "output" types, this
also allows various traits to have only one or the other. For example,
Rust's equivalent to Swift's Generator is std::iter::Iterator, which has
an associated type for the iterated element. And it makes a lot of sense
for Iterator to use an associated type for this instead of a type
parameter, because it's confusing to have a sequence that can yield
multiple different element types from a call to `seq.generate()` (or in
Rust's case, `seq.iter()`) which would require an explicit type
annotation. And it's even worse when you realize that most code that
iterates over sequences doesn't even care about the concrete generator
type, but the type annotation requires declaring that concrete type (the
alternative, declaring it as `GeneratorType<T>`, will wrap the concrete
type in a protocol object and incur the overhead of extra allocation +
dynamic function dispatch if the optimizer can't remove it).

tl;dr: I want both type parameters and associated types for protocols

-Kevin Ballard

On Thu, Dec 3, 2015, at 02:39 PM, David Hart wrote:
> I have been waiting a long time for something like this. I’m 100%
> behind this.
>
>> On 03 Dec 2015, at 23:12, Tal Atlas <me at tal.by> wrote:
>>
>> With the awesome expansion of protocol oriented programming that
>> swift has allowed, the lack of generic protocols has felt noticeably
>> lacking and painful in some cases. I made an in depth proposal here:
>> https://github.com/tal/swift-evolution/blob/tal/generic-protocol-proposal/proposals/NNNN-add-generic-protocols.md
>>
>> But the tl;dr is this:
>>
>> protocol Validator<TypeToValidate> {  var value: TypeToValidate { get
>> set }  var valueIfValid: TypeToValidate? { get } }
>>
>> struct FooStringValidator: Validator<String> {  //...
>> implementation }
>>
>> let stringValidator: Validator<String>
>>
>> Look forward to hearing some feedback.
>>
_______________________________________________
>> swift-evolution mailing list swift-evolution at swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>
> _________________________________________________
> swift-evolution mailing list swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution



Links:

  1. https://doc.rust-lang.org/stable/std/ops/trait.Add.html#tymethod.add
  2. https://doc.rust-lang.org/stable/std/ops/trait.Add.html
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20151203/e1684260/attachment.html>


More information about the swift-evolution mailing list