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

David Waite david at alkaline-solutions.com
Tue Jan 26 12:16:10 CST 2016

> On Jan 26, 2016, at 9:19 AM, Maximilian Hünenberger <m.huenenberger at me.com> wrote:
> Am 25.01.2016 um 23:58 schrieb David Waite <david at alkaline-solutions.com <mailto:david at alkaline-solutions.com>>:
> To have an implicit array of type Array<Number> that's what I've thought.
> I see your point but since covariance and contra-variance is a big topic there is a thread which discusses this in more detail:
> Make generics covariant - was: [swift-evolution] Make generics covariant and add generics to protocols

From what I’ve gathered the proposal for generic covariance is different. (I’m not super-keen on even exposing the functionality for generics still, but thats a side-point) :-)

let a:[UInt8] = [1,2,3]
let b:Array<where .Element : Number>  = a

b[0]= Int(1234)

In the proposal for generic covariance proposes a dynamic interface which would fail at runtime if unacceptable, as above. Most of the negative comments are that this does not allow one to write code which reasons about whether a method call will work or fail - array out of bounds can be avoided by manual array bounds checking, for instance. These sorts of undetectable runtime errors are what ErrorType is for, but you can’t retrofit throws behavior in based on the use of covariance either.

In my proposal, the compiler would not allow such a statement to proceed - the subscript functionality would be hidden because it is unsafe. This is actually similar to how Java handles wildcard-based call-site variance.

Then, by opening the existential type of element you can dynamically reason about whether the call should succeed or fail. e.g. something like:

let a:[UInt8] = [1,2,3]
let b:Array<where .Element : Number>  = a

typealias A = b.dynamicType
let c = b as! A

typealias E = b.Element
let val = Int(1234) as? E {
  c[0] = val
else { /* code to handle incompatible use */ }

Since I used Java as an example, I’ll go into more detail on how this proposal is different:

Java is a bit painful to have call-site variance in Java for four main reasons:
1. The error reporting is absolutely terrible
2. The generics system is not reified (it’s advisory), with a single implementation. The swift equivalent would be if the underlying only had a single kind of Array which held Any, and generics just added restrictions by the compiler onto the interface.
3. The generics system is incomplete. It is not possible to have arrays of generic types, for instance (a Java [ArrayList<Int>])
4. A type can be cast to any other generic constraint or even without generics with only warnings, which are maskable from being output by the compiler.

Because of these, I’ve seen several large projects (I won’t name names, but two of them are under Apache) where the generics definitions were unsound, yet nobody noticed because the developers just cast off the generics system completely when they ran into an edge case. 

For further example, one common anti-pattern I’ve seen for example is the state anti pattern - I have an implementation of an interface/protocol which lets me define a specific state object type as well as operations that will return a typed state object back as a result, and expect that same state object on future requests.

When working at the level of that interface, it is not possible for me to reason about the type of state object needed on requests, so all the requests defined that take in that typed state object will be hidden. Instead, people will cast to the non-generic version of the interface, which will accept any of the state objects, and pass this in. If you do this by calling a method which specifies the non-generic interface version, you don’t even need to write an explicit cast operator - but the compiler will still of course have to cast, and will still fail at runtime if it can’t.

The only way to dynamically determine whether or not the state is appropriate is to do RTTI (runtime type inspection) to look at the return value of the method and see if your State object’s runtime type is compatible. I’ve never seen anyone attempt this, because often they are so used to Java’s generics model being incomplete that they assume any generics errors are because of Java, not their declared type system.

>> <snip, to respond later>
>>> PS: Have you made a formal proposal already?
>> Not yet, I was hoping to get more feedback on the value of the idea before writing up all of the rules, which will take many hours of my time (to fail fast). There has been very little discussion so far.
>> My hope is to have a discussion on where the generics and constrained protocol systems should go for Swift 3 and beyond, but I suspect the core team is occupied by Swift 2.2 and the API guidelines discussion ATM. Type system changes are IMHO something that really warrant having a roadmap extending several releases into the future, and there are a number of ones being proposed by both the core team and evolution members (evolutioneers?) in general.
>> -DW
> We'll see. At least a better generics system is one of the main goals for Swift 3.
> - Maximilian

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

More information about the swift-evolution mailing list