Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unlock Existential Types for All Protocols #1176

Merged
Changes from 1 commit
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
a4a5adf
Added Draft.
filip-sakel Sep 4, 2020
a91f478
Review Updates
filip-sakel Sep 5, 2020
445c7f3
Review Updates.
filip-sakel Sep 5, 2020
40b05a8
Review Updates.
filip-sakel Sep 5, 2020
323a25a
Removed first example from Detailed Design.
filip-sakel Sep 6, 2020
a0057fe
Changes per review.
filip-sakel Sep 10, 2020
b80a579
Clarified current limitations in Motivation.
filip-sakel Sep 11, 2020
d27230f
Updated 'error message'.
filip-sakel Sep 11, 2020
1aa61d1
Updated formatting.
filip-sakel Sep 11, 2020
50a9916
Updated Formatting.
filip-sakel Sep 11, 2020
ecbbf4c
Replaced dots(...) with mock types.
filip-sakel Sep 11, 2020
97640d2
Fixed typo and clarified existential qualifiaction
filip-sakel Sep 12, 2020
8f198a8
Updated naming.
filip-sakel Sep 12, 2020
700ec25
Added note for Conflicting Types in Compositions.
filip-sakel Sep 25, 2020
3aa4b1f
Updated formatting.
filip-sakel Oct 18, 2020
86bb68c
Added Existentials in the Standard Library section
filip-sakel Oct 18, 2020
d4b9f44
Strengthened motivation by using Strideable.
filip-sakel Oct 18, 2020
65daf57
Rewrote proposal; this is a draft.
filip-sakel Nov 24, 2020
5218234
Added a link to SwiftUI.
filip-sakel Dec 3, 2020
2518bd3
Updated API Resilience.
filip-sakel Dec 3, 2020
1ca02f9
Added API/ABI Resilience Argument
filip-sakel Dec 3, 2020
29c19fd
Removed redundant words / Heterogenous Collections
filip-sakel Dec 3, 2020
e8cc29d
Removed unnecessary list of supported existentials
filip-sakel Dec 3, 2020
c919e8c
Removed unnecessary list of supported existentials
filip-sakel Dec 3, 2020
8e53926
Removed unnecessary connector in Motivation.
filip-sakel Dec 3, 2020
6eb13b0
Fixed type pointed out by Dave.
filip-sakel Dec 9, 2020
412dc49
Undo incorrect commit.
filip-sakel Dec 9, 2020
3b819cc
Removed unneeded Heterogenous Collections info.
filip-sakel Jan 3, 2021
be0d18d
Replaced "synthesized" with "generated".
filip-sakel Jan 3, 2021
e151181
Clarified existentials definition in Introduction.
filip-sakel Jan 3, 2021
bace022
Clarified Introduction section.
filip-sakel Jan 3, 2021
4b58873
A first round of amendments to the motivation
AnthonyLatsis Jan 23, 2021
0588dc1
Rephrase the introduction to clarify our intentions
AnthonyLatsis Jan 25, 2021
24a3ec2
Address review comments
AnthonyLatsis Feb 2, 2021
16e27a9
Rework the proposed solution and most of the motivation
AnthonyLatsis Feb 7, 2021
0e6f003
Reduce the Effects sections
AnthonyLatsis Feb 8, 2021
13bcd2c
Changes in wording and typo corrections.
filip-sakel Feb 9, 2021
93e80ca
Downside of -> Downside to
filip-sakel Feb 13, 2021
c6d414e
Update proposals/NNNN-unlock-existential-types-for-all-protocols.md
AnthonyLatsis Feb 14, 2021
7b3db88
Revert to "of existential type".
filip-sakel Feb 14, 2021
ffcbed6
Make Motivation more specific.
filip-sakel Feb 14, 2021
42e7918
Add the alternative of leaving the language as is.
filip-sakel Feb 24, 2021
3c93a1f
Update NNNN-unlock-existential-types-for-all-protocols.md
AnthonyLatsis Mar 19, 2021
1e584d4
Update NNNN-unlock-existential-types-for-all-protocols.md
AnthonyLatsis Mar 19, 2021
927fec1
Remove Heterogenous Collections section.
filip-sakel Mar 23, 2021
6056aba
Remove section for deemphasizing existentials.
filip-sakel Mar 23, 2021
be883bd
Decrease section about simplifying existentials.
filip-sakel Mar 24, 2021
016c458
Update NNNN-unlock-existential-types-for-all-protocols.md
AnthonyLatsis Mar 24, 2021
0ebd95c
Update NNNN-unlock-existential-types-for-all-protocols.md
AnthonyLatsis Mar 25, 2021
7ec8f02
Capitalize "c" in Non-conformable Existentials
filip-sakel Mar 25, 2021
d49cc87
Remove "Leave the Language in Its Existing State".
filip-sakel Mar 25, 2021
0534ab0
Revert title alteration.
filip-sakel Mar 25, 2021
df75054
Add direction for AnyHashable existential init.
filip-sakel Mar 25, 2021
61f6a8e
Update NNNN-unlock-existential-types-for-all-protocols.md
AnthonyLatsis Mar 30, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
236 changes: 236 additions & 0 deletions proposals/NNNN-unlock-existential-types-for-all-protocols.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
# Unlock Existentials for All Protocols

* Proposal: [SE-NNNN](NNNN-unlock-existential-types-for-all-protocols.md)
* Authors: [Anthony Latsis](https://github.com/AnthonyLatsis), [Filip Sakel](https://github.com/filip-sakel), [Suyash Srijan](https://github.com/theblixguy)
* Review Manager: TBD
* Status: **Awaiting implementation**
filip-sakel marked this conversation as resolved.
Show resolved Hide resolved


filip-sakel marked this conversation as resolved.
Show resolved Hide resolved
### Introduction

Swift currently offers the ability for protocols that meet certain criteria to be used as types. Trying to use an unsupported protocol as a type yields the error `[the protocol] can only be used as a generic constraint because it has 'Self' or associated type requirements`. This proposal aims to relax this artificial constraint imposed on such protocols.


### Motivation

Currently, any protocol that has `Self` or associated type requirements is not allowed to be used as a type. Initially, this restriction reflected technical limitations (as Joe states [here](https://forums.swift.org/t/lifting-the-self-or-associated-type-constraint-on-existentials/18025)); however, such limitations have now been alleviated. As a result, users are left unable to utilize a powerful feature for certain protocols. That’s evident in a plethora of projects. For instance, the Standard Library has existential types such as [`AnyHashable`](https://developer.apple.com/documentation/swift/anyhashable) and [`AnyCollection`](https://developer.apple.com/documentation/swift/anycollection) and SwiftUI has [`AnyView`](https://developer.apple.com/documentation/swiftui/anyview).
filip-sakel marked this conversation as resolved.
Show resolved Hide resolved

Generics are most often the best mechanism for type-level abstraction, which relies on the compiler knowing type information during compilation. However, type information is not always a gurantee, which is why the value-level abstraction of existential types is extremely useful in some cases.

One such case is heterogenous collections, which require value-level abstraction to store their elements of various types:

```swift
protocol Identifiable {
associatedtype ID: Hashable

var id: ID { get }
}
filip-sakel marked this conversation as resolved.
Show resolved Hide resolved

struct AnyIdentifiable {
typealias ID = AnyHashable

var id: ID { ... }
}

let myIdentifiables: [AnyIdentifiable]
filip-sakel marked this conversation as resolved.
Show resolved Hide resolved
```

Furthermore, dynamic environments are also known to lack type information. Therefore value-level abstraction can be exploited in cases such as previewing an application, where the application's components are dynamically replaced, in the file system where a file representing an unknown type might be stored, and in server environments, where various types could be exchanged between different computers.

All in all, supporting existential types for all protocols is useful for many situations that involve dynamicity. Moreover, there are many questions by language users asking about this behavior. Taking everything into consideration, we are confident that addressing this abnormality in the language will build a stronger foundation for [future additions](https://forums.swift.org/t/improving-the-ui-of-generics/22814) .


### Proposed Solution

We propose that the constraint prohibiting the use of some protocols as types be lifted. As a result, boilerplate code in many projects - especially libraries and frameworks - will be significantly reduced.


### Detailed Design

Should this proposal be accepted, the compiler would no longer differentiate between protocols that don’t have `Self` or associated type requirements and those that do. However, some restrictions would apply to the use of requirements referencing associated types as seen in the below examples.
filip-sakel marked this conversation as resolved.
Show resolved Hide resolved

#### Examples:

1. Regular Protocol

```swift
protocol Foo {
filip-sakel marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should avoid 'Foo' and 'Bar', because –– as Holly informed me during the writing of SE-293 –– it is actually an offensive term. Besides, I think we can find better examples with names and concepts that one is more likely to encounter in a real codebase.

I think a guide for intuitive code examples is the structured concurrency proposal's dinner preparation examples.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree we could (and I am going to) try harder on some examples, but I have mixed feelings on how reasonable that is for this particular one that shows a general approach to wrappers, or whether it is worth the time for some other rather subtle ones I would like to introduce in the "Detailed Design" section to cover the edge cases. These would most likely end up being an inseparable part of some large abstract interface if used in the wild.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that an overarching set of protocols, compositions, or use cases in general, used consistently across the proposal could help in both consistency and comprehensibility. So in the case of Detailed Design, we could provide descriptions and then some notable examples to help tie the thoroughly-discussed behavior together.

Of course, general examples are inherent in such an abstract feature –– which is not so much the case for the slightly more specific structured concurrency proposal. Structured concurrency is, of course, still an abstract concept; yet, the proposal authors did a great job of concentrating the concept into examples that showcase the its connection to a real-life task (cooking dinner).

Likewise, property wrappers, through review comments, were identified to belong to some categories, such as utility, proxy, domain-bound (or something to that effect); based on these categories, we settled on some wrappers that were used throughout the text, which I think helps with coherence.

For this proposal, I think the theme is dynamism. Existential types can take the place of generics I think we should focus on the "correct" use cases. The reason why I initially included AnyView is because every user of SwiftUI will have probably experienced the live previews as well. These is a great example of how an existential type acts as a bridge between the strictly-typed world of SwiftUI and the dynamic environment of Xcode previews.

var bar: Int { get }
}

let foo: Foo = ... ✅
filip-sakel marked this conversation as resolved.
Show resolved Hide resolved

let bar: Int = foo.bar ✅


extension Foo {
var baz: Self {
self
}

var opaqueSelf: some Foo {
self
}
}

let baz: some Foo =
foo.baz ✅
filip-sakel marked this conversation as resolved.
Show resolved Hide resolved

let opaqueSelf: some Foo =
foo.opaqueSelf ✅
```

2. Protocol with `Self` and Associated Type Requirements

```swift
protocol Foo {
associatedtype Bar

var bar: Bar { get }
}

let foo: Foo = ... ✅
filip-sakel marked this conversation as resolved.
Show resolved Hide resolved


let bar: Any = foo.bar ❌
// We don’t know what type
// `Bar` is on the existential
// type of `Foo`.
filip-sakel marked this conversation as resolved.
Show resolved Hide resolved


extension Foo {
var opaqueBar: some Any {
bar
}
// Note that it references
// the associated type `Bar`.
}

let opaqueBar: some Any =
foo.opaqueBar ❌
// Opaque result type don't
// type-erase; they just conceal
// the underlying value from the
// user. As a result, the above
// is not allowed.
```
filip-sakel marked this conversation as resolved.
Show resolved Hide resolved

3. Protocol with Known Associated Types

```swift
protocol Foo {
associatedtype Bar

var bar: Bar { get }
}

protocol RefinedFoo: Foo
where Bar == Int {}


let foo: RefinedFoo = … ✅

let intBar: Int = foo.bar ✅
// Here we know that the associated
// type `Bar` of `RefinedFoo` is `Int`.
```

4. Protocol Composition

```swift
protocol A {
associatedtype A

var a: A { get }
}

protocol RefinedA: A
where A == Int {}

protocol B {
associatedtype B

var b: B { get }
}


let ab: RefinedA & B = ... ✅


let a: Int = ab.a ✅

let b: some Any = ab.b ❌
// We don’t know what type
// `B` is on the type-erased
// value `ab`.
```


## Source compatibility

This is an additive change with _no_ impact on **source compatibility**.


## Effect on ABI stability

This is an additive change with _no_ impact on **ABI stability**.


## Effect on API resilience

This is an additive change with _no_ impact on **API resilience**.
filip-sakel marked this conversation as resolved.
Show resolved Hide resolved


### Alternatives Considered

We could leave Swift as is. That, however - as discussed in the Motivation section - produces boilerplate code and a lot of confusion for language users.


### Future Directions

#### Separate Existential Types from Protocols

To alleviate confusion between existential types and protocols it has been proposed that when referring to the former some different way be used. Some advocate for the modifier 'any' to serve that purpose: `any Foo`, while others propose parameterizing 'Any': `Any<Foo>`. Whatever the way of achieving this is, differentiation between the two would be useful as it would - among other reasons - prevent beginners from unknowingly using existential types, which can adversely affect performance.


#### Introduce Constraints for Existential Types

After introducing existential types for all protocols, constraining them seems like the next logical step. Constraining refers to constraining a protocol’s associated types which will, therefore, only be available to protocols that have unspecified associated types. These constraints would probably be the same-type constraint: `where A == B` and the conformance constraint: `where A: B`:

```swift
typealias Foo = Any<
Identifiable where .ID == String
>

typealias Bar = Any<
Identifiable where .ID: Comparable
>
```

#### Allow Accessing Associated Types

Currently, accessing associated types through a protocol's existential type is invalid. However, we could ease that constraint by replacing every associated type of the 'base' protocol with its existential type:

```swift
protocol Identifiable {
assciatedtype ID: Hashable

var id: ID { get }
}


let foo: Identifiable = …

let id: Any<Hashable> = foo.id ✅
```

#### Make Existential Types Extensible

Today, no protocol’s existential type can conform to the protocol itself (except for [`@objc` protocols](https://docs.swift.org/swift-book/LanguageGuide/Protocols.html#ID284)). This is quite unintuitive - as is evident by countless questions asking about it. Such a feature could automatically apply to protocols that lack initializers, static requirements and functions with parameters bound to `Self` (as discussed in [related post](https://forums.swift.org/t/allowing-self-conformance-for-protocols/39841)). To handle the cases that do not meet the aforementioned criteria for implicit conformance, the following syntax [has been proposed](https://forums.swift.org/t/improving-the-ui-of-generics/22814):

```swift
extension Any<Hashable>: Hashable {
}
```
Other protocols that _do_ meet these criteria would have existential types that automatically gain conformance to their corresponding protocol. In other words, a type such as `Error` would automatically gain support for conformance to the `Error` protocol.