Search code examples
swifttype-theory

What does "existential type" mean in Swift?


I am reading Swift Evolution proposal 244 (Opaque Result Types) and don't understand what the following means:

... existential type ...

One could compose these transformations by using the existential type Shape instead of generic arguments, but doing so would imply more dynamism and runtime overhead than may be desired.


Solution

  • An example of a protocol that can be used as an existential type is given in the evolution proposal itself:

    protocol Shape {
      func draw(to: Surface)
    }
    

    An example of using protocol Shape as an existential type would look like

    func collides(with: any Shape) -> Bool
    

    as opposed to using a generic argument Other:

    func collides(with: some Shape) -> Bool
    

    Since some keyword for parameter types became available only in Swift 5.7, in older versions this generic "non-existential" version code could be only written in a more verbose way as

    func collides<Other: Shape>(with: Other) -> Bool
    

    Important to note here that the Shape protocol is not an existential type by itself, only using it in "protocols-as-types" context as above "creates" an existential type from it. See this post from the member of Swift Core Team:

    Also, protocols currently do double-duty as the spelling for existential types, but this relationship has been a common source of confusion.

    This was also the motivation for introducing the any keyword that marks an existential type explicitly.

    Also, citing the Swift Generics Evolution article (I recommend reading the whole thing, which explains this in more details):

    The best way to distinguish a protocol type from an existential type is to look at the context. Ask yourself: when I see a reference to a protocol name like Shape, is it appearing at a type level, or at a value level? Revisiting some earlier examples, we see:

    func addShape<T: Shape>() -> T
    // Here, Shape appears at the type level, and so is referencing the protocol type
    
    var shape: any Shape = Rectangle()
    // Here, Shape appears at the value level, and so creates an existential type
    

    Deeper dive

    Why is it called an "existential"? I never saw an unambiguous confirmation of this, but I assume that the feature is inspired by languages with more advanced type systems, e.g. consider Haskell's existential types:

    class Buffer -- declaration of type class `Buffer` follows here
    
    data Worker x y = forall b. Buffer b => Worker {
      buffer :: b, 
      input :: x, 
      output :: y
    }
    

    which is roughly equivalent to this Swift snippet (if we assume that Swift's protocols more or less represent Haskell's type classes):

    protocol Buffer {}
    
    struct Worker<X, Y> {
      let buffer: any Buffer
      let input: X
      let output: Y
    }
    

    Note that the Haskell example used forall quantifier here. You could read this as "for all types that conform to the Buffer type class ("protocol" in Swift) values of type Worker would have exactly the same types as long as their X and Y type parameters are the same". Thus, given

    extension String: Buffer {}
    extension Data: Buffer {}
    

    values Worker(buffer: "", input: 5, output: "five") and Worker(buffer: Data(), input: 5, output: "five") would have exactly the same type.

    This is a powerful feature, which allows things such as heterogenous collections, and can be used in a lot more places where you need to "erase" an original type of a value and "hide" it under an existential type. Like all powerful features it can be abused and can make code less type-safe and/or less performant, so should be used with care.

    If you want even a deeper dive, check out Protocols with Associated Types (PATs), which currently can't be used as existentials for various reasons. There are also a few Generalized Existentials proposals being pitched more or less regularly, but nothing concrete as of Swift 5.3. In fact, the original Opaque Result Types proposal linked by the OP can solve some of the problems caused by use of PATs and significantly alleviates lack of generalized existentials in Swift.