[SOLVED] What does "existential type" mean in Swift?

Table of Contents

Issue

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 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<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.

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: 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: 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 types.

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, 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.

Answered By – Max Desiatov

Answer Checked By – Katrina (BugsFixing Volunteer)

Leave a Reply

Your email address will not be published.