[swift-evolution] Partially Constrained Protocols [Was: [Proposal] Separate protocols and interfaces]

Maximilian Hünenberger m.huenenberger at me.com
Tue Jan 26 11:32:47 CST 2016



> Am 26.01.2016 um 00:27 schrieb David Waite <david at alkaline-solutions.com>:
> 
> 
>> On Jan 25, 2016, at 2:52 PM, Maximilian Hünenberger <m.huenenberger at me.com> wrote:
>> 
>> I don't think that this would be confusing since `where` indicates that the type is used in a more generic way.
>> 
>> 
>> You've convinced me. I wanted to keep the generic syntax simple but having thought over it your proposal it much better in handling more complex cases without introducing additional syntax.
>> Also considering Swifts type inference where types with long names are easier to use.
>> 
>> 
>> A "small" overview of several examples:
>> 
>> Using the former example of CollectionType which is used as a more concrete type (using a "leading dot" which is the same as `Self.`).
>> 
>> // These examples are only a demonstration of how you could use collection type
>> 
>> CollectionType<where .Generator.Element == String, .Index == Int, .SubSequence: Self>
>> 
>> CollectionType<where .Generator.Element == String, .Index == Int>
>> 
>> CollectionType<where .Generator.Element == String>
>> 
>> // if having `Element` as associated type
>> CollectionType<where .Element == String>
> 
> One possible/fun option for syntax would be *if* Generics changed to have the types labelled. In that case, “where” becomes a keyword to distinguish a partial vs full constraint. 
> 
> For instance, Optional<T == BooleanType> would describe a concrete Optional holding some implementation of BooleanType, while Optional<where T:BooleanType> would be a generated protocol to umbrella every Optional that matches (e.g. Optional<T == Bool>, Optional<T == ObjCBool>, …)
> 
> Then the syntax could be identical, e.g. RawRepresentable<RawValue == Int> vs RawRepresentable<where RawValue:SignedNumberType>

That's a great idea. I wonder if the leading dot should be omitted since there is no "where" clause. In ambiguous situations you could then add "Self." or only the leading dot.
There could also be an exhaustiveness check if there is no "where", while forcing to write "where" if the protocol is partially applied.

> For protocols with self requirements you would likely force “where” syntax. I can say Equatable<Self == Int>, but the protocol can *only* be implemented by one type in the system, Int. I can’t think of a reason to support this.

Direct self constraints (Self == ...   and   Self: .....) should probably only used in "protocol<>".

> 
>> // covariance and invariance
>> 
>> protocol A {}
>> extension Int: A {}
>> 
>> let intArray: [Int] = [1, 2, 3]
>> let aArray: [A] = [1, 2, 3]
> 
> probably still:
>   let aArray: Array<where .Element: Int> = [1,2,3] 
> 
> if you really want covariance. The limitations (detailed in the other email reply) are such that it is better to have clear syntax so people recognize there may be ramifications.

I don't want implicit/inferred covariant types.
In this case invariant array:

let aArray: Array<where .Element == A> = [1,2,3] 
// which is equivalent to
let aArray: [A] = [1,2,3] 

> 
> I could however see a way of doing
>   let aCovariantArray: Array<where .Element: Int> = [1,2,3] 
>   let aArray = aCovaraintArray as [A]
> 
> being allowed, just as "intArray as [A]" works today due to internal behavior.
> 
> I detailed the ramifications of “is a concrete array of some specific implementation of A” behavior in the other half of my response - you are basically blocking a good deal of mutation behavior due to type safety. The restrictions are such that 
> 
>> var col1: CollectionType<where .Element == A>
>> col1 = intArray // doesn't work since Int != A
>> col1 = aArray // obviously works
> 
> The last line actually will fail as well if aArray is covariant on Element being A.

"aArray" is not covariant to "intArray" although each element is covariant to any type which conforms to "A".

> CollectionType “col1” is being constrained by Elements which *are* A, while aArray is a Collection type which is constrained by Elements being A *or* any subtype. if “intArray” doesn’t work directly and isn’t safe to work directly, why would discarding some of its type information by assigning to aArray make a difference? 

I was inspired from the current generic system of functions:

func takeCollection<C: CollectionType where C.Generator.Element == A>(c: C) {}

takeCollection(intArray) // fails
takeCollection(aArray)  // works


Or how would you interpret "CollectionType<where .Element == A>" in contrast to "CollectionType<where .Element: A>"?

> 
>> var col2: CollectionType<where .Element: A>
>> // both work since `Int` and `A` conform to `A` (currently in Swift 2.2 `A` does not conform to `A`, I don't know if this is a feature)
>> col2 = intArray
>> col2 = aArray
> 
> Yep!
> 
>> // with a concrete type using the example above:
>> 
>> // replace type of `col1` with (all replacements are equivalent)
>> `Array<A>` or `Array<where .Element == A>` or `[A]` or `[where .Element == A]`
>> 
>> // replace type of `col2` with (all replacements are equivalent)
>> `Array<where .Element: A>` or `[where .Element: A]`
> 
> I think yes, although col1 = aArray will still fail :D

Why should this fail? Both have the exact same type "[A]".

> 
>> // to be discussed: using many protocols together with protocol<>
>> 
>> // declaring a generic type T which is used in two protocols
>> protocol<where T, Self: SequenceType<where .Element == T>, Self: Indexable<where .Index == T>>
>> 
>> // although in this case it can be written as
>> SequenceType<where T, .Element == T, Self: Indexable<where .Index == T>>
>> 
>> Even though the latter one is shorter I'm skeptical about using `Self:` in protocol where clauses since at a first glance it implies that the type is only a `SequenceType`.
> 
> protocol<> is there for describing multiple protocols, so I’d go for the 1st.

+1 For using the first

Suggestion:
separate declaration of T to make it consistent with current generic syntax

protocol<T where Self: SequenceType<where .Element == T>, Self: Indexable<where .Index == T>>

This example could also be rewritten to:
protocol<where Self: SequenceType, Self: Indexable, Self.Element == Self.Index>


Another suggestion:
For completely unconstrained protocols with associated types "<>" could be added at the end of the type to indicate that they have associated types in comparison to protocols which don't have them.
=>
protocol<where Self: SequenceType<>, Self: Indexable<>, Self.Element == Self.Index>


- Maximilian

> 
> -DW
> 
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160126/b0cbd164/attachment.html>


More information about the swift-evolution mailing list