[swift-evolution] protocol can only be used as a generic constraint because it has Self or associated type requirements

Thorsten Seitz tseitz42 at icloud.com
Tue Dec 15 12:02:04 CST 2015


Shouldn’t we just narrow the type of protocols with associated types and/or Self types when the protocol is used as a type?


// (1) Associated types

protocol Bounds {
    func doSomething()
}

protocol Foo {
    
    // associated type with bounds
    typealias T where T: Bounds

    // getter is covariant, setter is contravariant
    var value: T { get set }
    
    // result is covariant
    func foo() -> T
    
    // arg is contravariant
    func bar(arg: T)
    func baz(arg: T) -> T
    
    // arg of arg is covariant
    func f(g: T -> Void)
}


var foo: Foo

// Covariant places of associated type T
// are ok and use bounds of T 
// (i.e. Any if no bounds are given).
// In our example the bounds are "Bounds"
let x: Bounds = foo.value
let y: Bounds = foo.foo()
foo.f { (z: Bounds) in z.doSomething() }

// Contravariant places of associated type T
// are forbidden, i.e. methods containing T
// as argument or setters of variables of type T
// are not allowed. In effect they are not part
// of the type Foo.
foo.value = x // ERROR: calling setter not allowed
foo.bar(x) // ERROR: not allowed
let z: Bounds = foo.baz(x) // ERROR: not allowed


// (2) Self types

protocol Bar {
    
    var value: Self { get set }
    func foo() -> Self
    func bar(arg: Self)
    func baz(arg: Self) -> Self
    func f(g: Self -> Void)
}

// Same as for associated types, just
// that the bounds for Self are the protocol
// itself, i.e. in this example: Bar


// (3) Constraining protocols with associated types or self types

protocol Child : Bounds {}

let foo2: Foo where T: Child // would work like above with narrower bounds Child
let foo3: Foo where T == Child // would make all methods and setters available with T == Child


// (4) Equatable and Set

// Equatable with associated type EquatableWith like suggested by Joe Groff
public protocol Equatable {
    typealias EquatesWith = Self where Self: EquatesWith
    func ==(lhs: EquatesWith, rhs: EquatesWith) -> Bool
}

protocol P : Hashable {
    typealias EquatesWith = P // fixing the associated type of Equatable
    typealias T where T: Bounds
    func foo() -> T
    var id: Int
}

func ==(lhs: P, rhs: P) -> Bool {
    return lhs.id == rhs.id // accessing id is ok
}

var p1: P
var p2: P
if p1 == p2 { // ok, because EquatableWith has been fixed to be of type P
    print("Ok!")
}

var ps: Set<P> // now ok, because == can be used on variables of type p
ps.insert(p1)
ps.insert(p2)
var bs: [Bounds] = ps.map { $0.foo() } // ok


With the proposed change of  
-Thorsten



> Am 14.12.2015 um 19:32 schrieb Joe Groff via swift-evolution <swift-evolution at swift.org>:
> 
> 
>> On Dec 14, 2015, at 7:40 AM, Dave Abrahams via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>> 
>> 
>>> On Dec 14, 2015, at 6:23 AM, Matthew Johnson via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>> 
>>> It is definitely possible to solve this problem for associated types by binding or constraining them.  The ML module system has a feature that is roughly analogous to this.  
>> 
>> Keep in mind that a language feature like binding/constraining associated types is not a complete solution for real use-cases, so we would still need AnySequence<Element> in the library.  Otherwise it would be SequenceType<Generator: SomeGenerator>, which binds a type that you don't actually want to expose.
> 
> With generic typealiases, you could say something like this:
> 
> typealias AnySequence<Element> =
>   protocol<SequenceType where Generator.Element == Element>
> 
> However, once we add 'where' requirements to protocols, it seems to me 'Element' really ought to be a direct requirement of SequenceType, like this:
> 
> protocol SequenceType {
>   typealias Element
>   typealias Generator: GeneratorType
>     where Element == Generator.Element
> }
> 
> which would save generic code from having to splatter '.Generator.Element' everywhere, and make unspecialized code more efficient, since the type metadata for Element would be directly available from the SequenceType protocol witness table instead of needing an extra indirection through to its Generator's GeneratorType table.
> 
> -Joe
> 
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20151215/2206d41b/attachment.html>


More information about the swift-evolution mailing list