[swift-evolution] [swift-evolution-announce] [Review] SE-0089: Replace protocol<P1, P2> syntax with Any<P1, P2>

Dave Abrahams dabrahams at apple.com
Wed Jun 8 17:22:37 CDT 2016


on Wed Jun 08 2016, Austin Zheng <austinzheng-AT-gmail.com> wrote:

> FWIW my opinion is that existentials either shouldn't be allowed to stand
> in for generic type parameters, or Dave's option #1 if they are.

Don't you mean #2?  Otherwise I'm confused.  #1 is the one that
prohibits more usages.

> The implied promise of a generic type parameter T right now is that T
> always stands for the same concrete type (modulo things like passing
> in a subclass where a class would do), and likewise for all of T's
> associated types (T.Foo is always the same type everywhere in the
> context where T is valid). This is what makes using anything with
> 'self' requirements in a generic context sound. Allowing existentials
> to satisfy T would violate that constraint.

Not if you consider Any<Collection where Element == Int> to be a
concrete type.  Concrete w.r.t. to a generic parameter means something
different from Concrete w.r.t. subtyping.

> Relaxing these semantics would make it too easy to write code that
> traps at runtime "without the user having to reach" (to paraphrase
> Jordan from the "Swift philosophy" thread). Anyone who really really
> wants to write code that is 'compile-time unsound' in this way should
> have to explicitly type erase using concrete wrappers.

I'd really like to see the use-cases, to make sure that these restricted
existentials will be useful enough to be worth implementing.

>
>
> Best,
> Austin
>
> On Wed, Jun 8, 2016 at 2:37 PM, Austin Zheng <austinzheng at gmail.com> wrote:
>
>> We might be talking past each other. I think Matthew is talking about
>> using an existential outside the context of generic functions. For example,
>> something like this should be trap-proof (as long as 'x' is immutable,
>> which it is in this example):
>>
>> func copySequenceIntoArray(x: Any<Sequence where .Iterator.Element ==
>> Int>) -> [Int] {
>> var buffer : [Int] = []
>>         // Stupid implementation to make a point
>> var iterator : x.Iterator = x.makeIterator()
>> while true {
>> let nextItem : Int? = iterator.next()
>> if let nextItem = nextItem {
>> buffer.append(nextItem)
>> } else {
>> return buffer
>> }
>> }
>> }
>>
>> Even this would never trap as well:
>>
>> func copySequenceIntoArray<T>(x: Any<Sequence where .Iterator.Element ==
>> T>) -> [T] {
>> var buffer : [T] = []
>> for item in x {
>> buffer.append(item)
>> }
>> return buffer
>> }
>>
>> Where we run into difficulty is something like this (forgive my abuse of
>> the collections API; I don't remember all the new indexing APIs off the top
>> of my head):
>>
>> func doSomething<T : Collection where T.Element == Int>(x: T, y: T) {
>> // Get indexes out of x and use them to index into y
>> var idx = x.startIndex
>> while (idx != x.endIndex || idx != y.endIndex) {
>> print(x[idx])
>> print(y[idx])
>> idx = x.nextIndex(idx)
>> }
>> }
>> let someSeq : Any<Collection where .Element == Int> = // ...
>> let anotherSeq : Any<Collection where .Element == Int> = // ...
>> // Trouble!
>> // someSeq and anotherSeq are the same existential type
>> // But the concrete index types within each of the existential variables
>> may be different
>> doSomething(someSeq, anotherSeq)
>>
>> It's this situation (using an existential type to fulfill a generic type
>> parameter constrained to the same requirements that comprise that
>> existential) that requires either of the two options that Dave presented,
>> due to our lack of compile-time type information about the fulfilling
>> type's associated types.
>>
>> Best,
>> Austin
>>
>> On Wed, Jun 8, 2016 at 2:33 PM, Matthew Johnson via swift-evolution <
>> swift-evolution at swift.org> wrote:
>>
>>>
>>>
>>> Sent from my iPad
>>>
>>> > On Jun 8, 2016, at 3:16 PM, Dave Abrahams via swift-evolution <
>>> swift-evolution at swift.org> wrote:
>>> >
>>> >
>>> >> on Wed Jun 08 2016, Thorsten Seitz <swift-evolution at swift.org> wrote:
>>> >>
>>> >> Ah, thanks, I forgot!  I still consider this a bug, though (will have
>>> >> to read up again what the reasons are for that behavior).
>>> >
>>> > Yes, but in the case of the issue we're discussing, the choices are:
>>> >
>>> > 1. Omit from the existential's API any protocol requirements that depend
>>> >   on Self or associated types, in which case it *can't* conform to
>>> >   itself because it doesn't fulfill the requirements.
>>>
>>> They don't need to be omitted.  They are exposed in different ways
>>> depending on how the existential is constrained.  Austin's proposal was
>>> originally written to omit some members but it was modified based on
>>> feedback from Doug Gregor IIRC (Austin, is that right?).  Now it contains
>>> examples showing how these members are made available in a safe way.   Some
>>> members may still not be usable because you can't form an argument but IIRC
>>> the suggestion was that they be exposed anyway for consistency.
>>>
>>> >
>>> > 2. Erase type relationships and trap at runtime when they don't line up.
>>> >
>>> > Matthew has been arguing against #2, but you can't “fix the bug” without
>>> > it.
>>> >
>>> >>
>>> >> -Thorsten
>>> >>
>>> >>> Am 08.06.2016 um 21:43 schrieb Austin Zheng <austinzheng at gmail.com>:
>>> >>>
>>> >>> It's not possible, even with Swift's current implementation of
>>> >>> existentials. A protocol type P isn't considered to conform to
>>> >>> itself, thus the following is rejected:
>>> >>>
>>> >>> let a : MyProtocol = // ...
>>> >>> func myFunc<T : MyProtocol>(x: T) {
>>> >>>  // ....
>>> >>> }
>>> >>> myFunc(a) // "Cannot invoke 'myFunc' with an argument list of type
>>> MyProtocol"
>>> >>>
>>> >>> Changing how this works is probably worth a proposal by itself.
>>> >>>
>>> >>> Austin
>>> >>>
>>> >>>
>>> >>> On Wed, Jun 8, 2016 at 12:34 PM, Thorsten Seitz via swift-evolution
>>> >>> <swift-evolution at swift.org
>>> >>> <mailto:swift-evolution at swift.org>>
>>> >>> wrote:
>>> >>>
>>> >>>> Am 08.06.2016 um 20:33 schrieb Dave Abrahams via swift-evolution
>>> >>>> <swift-evolution at swift.org
>>> >>>> <mailto:swift-evolution at swift.org>>:
>>> >>>>
>>> >>>>
>>> >>>> on Tue Jun 07 2016, Matthew Johnson <matthew-AT-anandabits.com>
>>> wrote:
>>> >>>>
>>> >>>>>> On Jun 7, 2016, at 9:15 PM, Dave Abrahams
>>> >>>>>> <dabrahams at apple.com
>>> >>>>>> <mailto:dabrahams at apple.com>>
>>> >>>>>> wrote:
>>> >>>>>>
>>> >>>>>>
>>> >>>>>> on Tue Jun 07 2016, Matthew Johnson <matthew-AT-anandabits.com
>>> >>>>>> <http://matthew-at-anandabits.com/
>>> >>>>>> <http://matthew-at-anandabits.com/>>> wrote:
>>> >>>>>
>>> >>>>>>>> On Jun 7, 2016, at 4:13 PM, Dave Abrahams via swift-evolution
>>> >>>>>>>> <swift-evolution at swift.org
>>> >>>>>>>> <mailto:swift-evolution at swift.org>>
>>> >>>>>>>> wrote:
>>> >>>>>>>>
>>> >>>>>>>>
>>> >>>>>>>> on Tue Jun 07 2016, Matthew Johnson
>>> >>>>>>>> <swift-evolution at swift.org
>>> >>>>>>>> <mailto:swift-evolution at swift.org>>
>>> >>>>>>>> wrote:
>>> >>>>>>>
>>> >>>>>>>>>> , but haven't realized
>>> >>>>>>>>>> that if you step around the type relationships encoded in Self
>>> >>>>>>>>>> requirements and associated types you end up with types that
>>> appear to
>>> >>>>>>>>>> interoperate but in fact trap at runtime unless used in
>>> exactly the
>>> >>>>>>>>>> right way.
>>> >>>>>>>>>
>>> >>>>>>>>> Trap at runtime?  How so?  Generalized existentials should
>>> still be
>>> >>>>>>>>> type-safe.
>>> >>>>>>>>
>>> >>>>>>>> There are two choices when you erase static type relationships:
>>> >>>>>>>>
>>> >>>>>>>> 1. Acheive type-safety by trapping at runtime
>>> >>>>>>>>
>>> >>>>>>>> FloatingPoint(3.0 as Float) + FloatingPoint(3.0 as Double) //
>>> trap
>>> >>>>>>>>
>>> >>>>>>>> 2. Don't expose protocol requirements that involve these
>>> relationships,
>>> >>>>>>>> which would prevent the code above from compiling and prevent
>>> >>>>>>>> FloatingPoint from conforming to itself.
>>> >>>>>>>>
>>> >>>>>>>>> Or are you talking about the hypothetical types / behaviors
>>> people
>>> >>>>>>>>> think they want when they don’t fully understand what is
>>> happening...
>>> >>>>>>>>
>>> >>>>>>>> I don't know what you mean here.  I think generalized
>>> existentials will
>>> >>>>>>>> be nice to have, but I think most people will want them to do
>>> something
>>> >>>>>>>> they can't possibly do.
>>> >>>>>>>
>>> >>>>>>> Exactly.  What I meant is that people think they want that
>>> expression
>>> >>>>>>> to compile because they don’t understand that the only thing it
>>> can do
>>> >>>>>>> is trap.  I said “hypothetical” because producing a compile time
>>> error
>>> >>>>>>> rather than a runtime trap is the only sane thing to do.  Your
>>> comment
>>> >>>>>>> surprised me because I can’t imagine we would move forward in
>>> Swift
>>> >>>>>>> with the approach of trapping.
>>> >>>>>>
>>> >>>>>> I would very much like to be able to create instances of
>>> “Collection
>>> >>>>>> where Element == Int” so we can throw away the wrappers in the
>>> stdlib.
>>> >>>>>> That will require some type mismatches to be caught at runtime via
>>> >>>>>> trapping.
>>> >>>>>
>>> >>>>> For invalid index because the existential accepts a type erased
>>> index?
>>> >>>>
>>> >>>> Exactly.
>>> >>>>
>>> >>>>> How do you decide where to draw the line here?  It feels like a very
>>> >>>>> slippery slope for a language where safety is a stated priority to
>>> >>>>> start adopting a strategy of runtime trapping for something as
>>> >>>>> fundamental as how you expose members on an existential.
>>> >>>>
>>> >>>> If you don't do this, the alternative is that “Collection where
>>> Element
>>> >>>> == Int” does not conform to Collection.  That's weird and not very
>>> >>>> useful.  You could expose all the methods that were on protocol
>>> >>>> extensions of Collection on this existential, unless they used
>>> >>>> associated types other than the element type.  But you couldn't pass
>>> the
>>> >>>> existential to a generic function like
>>> >>>>
>>> >>>>  func scrambled<C: Collection>(_ c: C) -> [C.Element]
>>> >>>
>>> >>> I don’t understand. Why couldn’t an existential be passed to that
>>> function?
>>> >>>
>>> >>> -Thorsten
>>> >>>
>>> >>>
>>> >>>
>>> >>>>
>>> >>>>> IMO you should *have* to introduce unsafe behavior like that
>>> manually.
>>> >>>>
>>> >>>> Collection where Element == Int & Index == *
>>> >>>>
>>> >>>> ?
>>> >>>>
>>> >>>>> Collection indices are already something that isn’t fully statically
>>> >>>>> safe so I understand why you might want to allow this.
>>> >>>>
>>> >>>> By the same measure, so are Ints :-)
>>> >>>>
>>> >>>> The fact that a type's methods have preconditions does *not* make it
>>> >>>> “statically unsafe.”
>>> >>>>
>>> >>>>> But I don’t think having the language's existentials do this
>>> >>>>> automatically is the right approach.  Maybe there is another
>>> approach
>>> >>>>> that could be used in targeted use cases where the less safe
>>> behavior
>>> >>>>> makes sense and is carefully designed.
>>> >>>>
>>> >>>> Whether it makes sense or not really depends on the use-cases.
>>> There's
>>> >>>> little point in generalizing existentials if the result isn't very
>>> useful.
>>> >>>> The way to find out is to take a look at the examples we currently
>>> have
>>> >>>> of protocols with associated types or Self requirements and consider
>>> >>>> what you'd be able to do with their existentials if type
>>> relationships
>>> >>>> couldn't be erased.
>>> >>>>
>>> >>>> We have known use-cases, currently emulated in the standard library,
>>> for
>>> >>>> existentials with erased type relationships.  *If* these represent
>>> the
>>> >>>> predominant use cases for something like generalized existentials, it
>>> >>>> seems to me that the language feature should support that.  Note: I
>>> have
>>> >>>> not seen anyone build an emulation of the other kind of generalized
>>> >>>> existential.  My theory: there's a good reason for that :-).
>>> >>>>
>>> >>>> --
>>> >>>> Dave
>>> >>>> _______________________________________________
>>> >>>> swift-evolution mailing list
>>> >>>> swift-evolution at swift.org
>>> >>>> <mailto:swift-evolution at swift.org>
>>> >>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>> >>>> <https://lists.swift.org/mailman/listinfo/swift-evolution>
>>> >>>
>>> >>> _______________________________________________
>>> >>> swift-evolution mailing list
>>> >>> swift-evolution at swift.org
>>> >>> <mailto:swift-evolution at swift.org>
>>> >>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>> >>> <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
>>> >
>>> > --
>>> > Dave
>>> >
>>> > _______________________________________________
>>> > 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
>>>
>>
>>

-- 
Dave


More information about the swift-evolution mailing list