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

Matthew Johnson matthew at anandabits.com
Tue Jun 7 13:27:48 CDT 2016


> On Jun 7, 2016, at 12:51 PM, Dave Abrahams <dabrahams at apple.com> wrote:
> 
> 
> on Tue Jun 07 2016, Matthew Johnson <matthew-AT-anandabits.com> wrote:
> 
>>> On Jun 6, 2016, at 12:22 AM, Dave Abrahams <dabrahams at apple.com>
>> wrote:
>>> 
>>> 
>>> on Sun Jun 05 2016, Matthew Johnson <matthew-AT-anandabits.com
>> <http://matthew-at-anandabits.com/>> wrote:
>>> 
>> 
>>>> Sent from my iPhone
>>>> 
>>>>> On Jun 5, 2016, at 3:51 PM, Dave Abrahams via swift-evolution
>>>>> <swift-evolution at swift.org> wrote:
>>>>> 
>>>>> 
>>>>>> on Wed May 25 2016, Matthew Johnson <swift-evolution at swift.org> wrote:
>>>>>> 
>>>>>> Sent from my iPad
>>>>>> 
>>>>>>> On May 25, 2016, at 12:10 PM, Jordan Rose via swift-evolution
>>>>>>> <swift-evolution at swift.org> wrote:
>>>>>>> 
>>>>>>> 
>>>>>>>>> On May 25, 2016, at 05:27, Brent Royal-Gordon via swift-evolution
>>>>>>>>> <swift-evolution at swift.org> wrote:
>>>>>>>>> 
>>>>>>>>> AFAIK an existential type is a type T with type parameters that
>>>>>>>>> are still abstract (see for example
>>>>>>>>> https://en.wikipedia.org/wiki/Type_system#Existential_types),
>>>>>>>>> i.e. have not been assigned concrete values.
>>>>>>>> 
>>>>>>>> My understanding is that, in Swift, the instance used to store
>>>>>>>> something whose concrete type is unknown (i.e. is still abstract),
>>>>>>>> but which is known to conform to some protocol, is called an
>>>>>>>> "existential". Protocols with associated values cannot be packed
>>>>>>>> into normal existentials because, even though we know that the
>>>>>>>> concrete type conforms to some protocol, the associated types
>>>>>>>> represent additional unknowns, and Swift cannot be sure how to
>>>>>>>> translate uses of those unknown types into callable members. Hence,
>>>>>>>> protocols with associated types are sometimes called
>>>>>>>> "non-existential".
>>>>>>>> 
>>>>>>>> If I am misusing the terminology in this area, please understand
>>>>>>>> that that's what I mean when I use that word.
>>>>>>> 
>>>>>>> We’re not consistent about it, but an “existential value” is a value
>>>>>>> with protocol or protocol composition type. My mnemonic for this is
>>>>>>> that all we know is that certain operations exist (unlike a generic
>>>>>>> value, where we also have access to the type). John could explain it
>>>>>>> more formally. We sometimes use “existentials” as a (noun) shorthand
>>>>>>> for “existential value”.
>>>>>>> 
>>>>>>> In the compiler source, all protocols and protocol compositions are
>>>>>>> referred to as “existential types”, whether they have associated
>>>>>>> types or not. Again, a protocol asserts the existence (and
>>>>>>> semantics) of various operations, but nothing else about the
>>>>>>> conforming type. (Except perhaps that it’s a class.) All protocols
>>>>>>> are thus “existential types” whether or not the language supports
>>>>>>> values having that type.
>>>>>>> 
>>>>>>> It is incorrect to say that protocols with associated types (or
>>>>>>> requirements involving Self) are “non-existential”.
>>>>>> 
>>>>>> I haven't heard people using this term myself, but I imagine they
>>>>>> probably mean "can't form an existential value with the protocol".
>>>>>> There certainly appears to be a lot of confusion in the community with
>>>>>> many not realizing that this is a temporary limitation of the
>>>>>> implementation, not a necessary fact.
>>>>> 
>>>>> As far as I know there is no known way to make protocols with Self
>>>>> requirements usefully “existentiable,” or whatever you want to
>> call it.
>>>>> So unless I'm missing something, in this respect, the limitation
>> is not
>>>>> temporary at all.
>>>> 
>>>> Take a look at the Equatable example in the opening existentials
>>>> section of Doug's manifesto:
>>>> 
>> https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160229/011666.html
>>> 
>>> Yes, note that I said *usefully* “existential.”
>> 
>> Fair enough.  But note that I was only talking about the inability to
>> form and open such an existential which appears likely to be a
>> temporary limitation given the generics manifesto (of course things
>> could change).
>> 
>>> While we can of course downcast in this way, you have to handle the
>>> failure case and it's not like you can use this to make a
>>> heterogeneous Set<Hashable>.  AFAICT, this is not at all like what
>>> happens with associated types, where Collection<Element: Int> has
>>> obvious uses.
>> 
>> We can’t use this to form Set<Hashable> because existentials don’t
>> conform to the protocol.  I know there is complexity in implementing
>> this and it is not possible to synthesize conformance of the
>> existential for all protocols, but AFAIK it isn’t a settled point that
>> we won’t try to improve the situation in some way.  
> 
> Of course.  I'm just trying to point out that such existentials are
> likely to be a whole lot less useful than many people might think.
> 
>> Maybe we can make progress here somehow.
> 
> Maybe.  It's a research project.
> 
>> In the meantime, we can make a simple wrapper type to provide the
>> required conformance and make a Set<HashableWrapper> that allows us to
>> put Hashable into a Set.  This isn’t ideal but it is a lot less
>> boilerplate than is involved in manual type erasure today where you
>> need to define a struct, base class, and wrapper class.  I’d say
>> that’s a win even if we’d like to do better in the future.
>> 
>> struct HashableWrapper: Hashable {
>>    var value: Hashable
>>    public var hashValue: Int { return base.hashValue }
>> }
>> 
>> public func ==(lhs: HashableWrapper, res: HashableWrapper) -> Bool {
>>    if let lhsValue = lhs.value openas T { // T is a the type of
>> lhsValue, a copy of the value stored in lhs
>>        if let rhsValue = rhs.value as? T {      // is res also a T?
>>            // okay: lhsValue and rhsValue are both of type T, which
>> we know is Equatable
>>            if lhsValue == rhsValue { 
>>                return true
>>            }
>>        } 
>>    }
>>    return false
>> }
>> 
>> (We could also do this with a generic Wrapper<T> and conditional
>> conformance in an `extension Wrapper: Hashable where T == Hashable` if
>> we don’t want a bunch of wrapper types laying around)
> 
> I don't think I"ve made my point very well.  Equatable (the
> Self-requirement part of Hashable) has a simple answer when the types
> don't match, as I noted in the POP talk.

I agree this is a simple case with an obvious answer.

But there are other cases where existentials formed from protocols with Self requirements will be useful (or more precisely, specific members of those existentials).  For example, a member with a Self return type will be exposed on the existential as returning another existential with the same constraints.

>  Let me put it differently:
> existentializing a protocol with Self requirements comes with
> limitations that I'm betting most people haven't recognized.  For
> example, you won't be able to add two arbitrary FloatingPoint
> existentials.  I think many people view working with existentials as
> “easier” or “cleaner” than working with generics

I agree with this.  One thing generalized existentials will hopefully do is help to clarify people’s thinking.  They will be able to form the existential they want.  It just may not expose the operations they expect in a way they expect.  Hopefully that will prompt them to think more carefully about what they are trying to do and realize that it may not actually make sense.

I definitely don’t agree with those who would say existentials are “easier” or “cleaner” than generics in the general case.  I think that perspective might arise from experience working with simple protocols / interfaces in OO languages where you can’t encode more sophisticated type relationships at all.  I can see how in those simple cases some people might view generics as unnecessarily noisy or complex syntactically.

> , 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.  Or are you talking about the hypothetical types / behaviors people think they want when they don’t fully understand what is happening...

> 
> -- 
> Dave



More information about the swift-evolution mailing list