This idea comes from GHC's ExistentialQuantification
extension to the Haskell Language. My original pitch was horribly unclear, and suggested a confusing syntax, so if you read it in its original form, find it revised below:
Context
Let's start with a simplified version of the IteratorProtocol
protocol from the standard library, restricted to iterating only over Int
values:
protocol IteratorOverInt {
mutating func next() -> Int?
}
It is completely valid to store instances of types conforming to IteratorOverInt
, without needing to know the concrete type at compile time. So we could very well have something like:
struct ContainsIntIterator {
var iterator: IteratorOverInt
... // Other fields
}
This struct
declaration is completely valid, and its type is completely independent of the concrete (runtime) type of iterator
. But now say we are using the IteratorProtocol
from the standard library:
struct ContainsIntIterator {
var iterator: ... // What do we do?
...
}
Outside of its generics system, Swift has no built-in way to express what we would like iterator
to be: an instance of a type conforming to IteratorProtocol
, where the Element
associated type in the conformance is Int
. We could solve the problem by making ContainsIntIterator
generic:
struct ContainsIntIterator<I: IteratorProtocol> where I.Element == Int {
var iterator: I
...
}
This allows us to specify the type of iterator
exactly as we want it, but in doing so we must parameterize the ContainsIntIterator
struct by the concrete type of I
. In some cases this makes sense, but in others, it may simply complicate code: what if a user of ContainsIntIterator
has no reason to care about the contained iterator's concrete type?
You can apply targeted solutions per protocol, such as the standard library's AnyIterator<Element>
, but I pitch a new language feature that would provide first-class support for this and other use-cases.
The Pitch
What if Swift brought all the features of generics to stored properties and variables? What if we could write this:
struct ContainsIntIterator {
var iterator: (forall<I: IteratorProtocol> I where I.Element == Int)
...
}
So what would this mean? iterator
would be an instance of some unknown (at compile time) type I
, with constraints given in the familiar where
syntax. The forall
would exist to introduce new type variables, just as generics do, but would not allow them to escape beyond the forall
context.
Within this struct
's methods, you would be able to use iterator
exactly as you would in the generic-struct
version; the difference is that ContainsIntIterator
no longer "leaks" the concrete type of iterator
, because neither it nor any of its consumers actually care what that is.
Importantly, I am not suggesting anything similar to C++'s templated variables (where a template parameter would be provided at each usage site).
Other Use Cases
Admittedly the above example was quite contrived. As something more concrete (and as something which I do not know how to express at all in current Swift), I present a Parser
protocol for a parser-combinator I'm working on:
protocol Parser {
associatedtype Result
func parse<C: Collection>(_ input: C) throws -> Result where C.Element == Character
}
A Parser
is a type that knows how to parse
a collection of characters into some Result
. It should not care about the concrete type of the selection, and so the required parse
method is generic over all Collection
s.
Since my parsers will be constructed from a large number of re-usable and generic sub-parsers, their concrete types will be quite large. I would like a way to completely erase the type of a parser while maintaining its functionality, primarily to return parsers from functions, but also to support arrays of parsers with my combinators.
I set out to write an AnyParser<Result>
, but there is no way to erase the generic type of parse
; there is no way to store a generic closure. Of course, I could make an AnyParser<Result, C: Collection>
, but now AnyParser
is not a Parser
!
With my pitch, I would not need any such AnyParser
type; if I wanted a function foo
that returned any kind of parser for an Int
, I could write its signature as:
func foo(...) -> (forall<P: Parser> P where P.Result == Int)