You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: content/existential-containers.md
+95-40Lines changed: 95 additions & 40 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -45,7 +45,7 @@ given Hexagon is Polygon: ...
45
45
```
46
46
47
47
Defining `Polygon` as a type class rather than an abstract class to be inherited allows us to retroactively state that squares are polygons without modifying the definition of `Square`.
48
-
Sticking to subtyping only would require the definition of an inneficient and verbose wrapper class.
48
+
Sticking to subtyping would require the definition of an inneficient and verbose wrapper class.
49
49
50
50
Alas, type classes offer limited support for type erasure–the eliding of some type information at compile-time.
51
51
Hence, it is difficult to manipulate heterogeneous collections or write procedures returning arbitrary values known to model a particular concept.
@@ -65,83 +65,138 @@ In other words, it is impossible to call `largest` with an heterogeneous sequenc
65
65
## Proposed solution
66
66
67
67
The problems raised above can be worked around if, instead of using generic parameters with a context bound, we use pairs bundling a value with its conformance witness.
68
-
For example, we can rewrite `largest` as follows:
68
+
In broad strokes, our solution generalizes the following possible implementation of `largest`:
A pair `(Any, PolygonWitness)` conceptually represents a type-erased polygon.
76
-
We call this pair an _existential container_ and the remainder of this SIP explains how to express this idea in a single, type-safe abstraction by leveraging Scala 3 features.
80
+
The type `AnyPolygon` conceptually represents a type-erased polygon.
81
+
It consists of a pair containing some arbitrary value as well as a witness of that value's type being a polygon.
82
+
We call this pair an _existential container_, as a nod to a similar feature in Swift, and the remainder of this SIP explains how to express this idea in a single, type-safe abstraction.
77
83
78
84
### Specification
79
85
80
-
As mentioned above, an existential container is merely a pair containing a value and a witness of its conformance to some concept(s).
81
-
Expressing such a value in Scala is easy: just write `(Square(1) : Any, summon[Square is Polygon] : Any)`.
82
-
This encoding, however, does not allow the selection of any method defined by `Polygon` without an unsafe cast due to the widening applied on the witness.
83
-
Fortunately, this issue can be addressed with path dependent types:
Given a type class `C`, an instance `Containing[C]` is an existential container, similar to `AnyPolygon` shown before.
115
+
The context bound on the definition of the `Value` member provides a witness of `Value`'s conformance to `C` during implicit resolution when a method of the `value` field is selected.
116
+
The companion object of `Containing` provides basic support to create containers ergonomically.
117
+
For instance:
102
118
103
-
A justification of why the proposal will preserve backward binary and TASTy compatibility. Changes are backward binary compatible if the bytecode produced by a newer compiler can link against library bytecode produced by an older compiler. Changes are backward TASTy compatible if the TASTy files produced by older compilers can be read, with equivalent semantics, by the newer compilers.
If it doesn't do so "by construction", this section should present the ideas of how this could be fixed (through deserialization-time patches and/or alternative binary encodings). It is OK to say here that you don't know how binary and TASTy compatibility will be affected at the time of submitting the proposal. However, by the time it is accepted, those issues will need to be resolved.
124
+
To further improve usability, we propose to let the compiler inject the selection of the `value` field implicitly when a method of `Containing[C]` is selected.
125
+
That way, one can simply write `xs.maxByOption(_.area)` in the above example, resulting in quite idiomatic scala.
106
126
107
-
This section should also argue to what extent backward source compatibility is preserved. In particular, it should show that it doesn't alter the semantics of existing valid programs.
A discussion of how the proposal interacts with other language features. Think about the following questions:
141
+
### Compatibility
112
142
113
-
- When envisioning the application of your proposal, what features come to mind as most likely to interact with it?
114
-
- Can you imagine scenarios where such interactions might go wrong?
115
-
- How would you solve such negative scenarios? Any limitations/checks/restrictions on syntax/semantics to prevent them from happening? Include such solutions in your proposal.
143
+
The change in the syntax does not affect any existing code and therefore this proposal has no impact on source compatibility.
116
144
117
-
### Other concerns
145
+
The semantics of the proposed feature is fully expressible in Scala.
146
+
Save for the implicit addition of `.value` on method selection when the receiver is an instance of `Containing[C]`, this proposal requires no change in the language.
147
+
As a result, it has no backward binary or TASTy compatibility consequences.
118
148
119
-
If you think of anything else that is worth discussing about the proposal, this is where it should go. Examples include interoperability concerns, cross-platform concerns, implementation challenges.
149
+
### Feature Interactions
120
150
121
-
### Open questions
151
+
The proposed feature is meant to interact with implicit search, as currently implemented by the language.
152
+
More specifically, given an existential container `c`, accessing `c.value`_opens_ the existential while retaining its type `c.Value`, effectively keeping an _anchor_ (i.e., the path to the scope of the witness) to the interface of the type class.
122
153
123
-
If some design aspects are not settled yet, this section can present the open questions, with possible alternatives. By the time the proposal is accepted, all the open questions will have to be resolved.
154
+
Since no change in implicit resolution is needed, this proposal cannot create unforeseen negative interactions with existing features.
124
155
125
-
##Alternatives
156
+
### Open questions
126
157
127
-
This section should present alternative proposals that were considered. It should evaluate the pros and cons of each alternative, and contrast them to the main proposal above.
158
+
One problem not addressed by the proposed encoding is the support of multiple type classes to form the interface of a specific container.
159
+
For example, one may desire to create a container of values whose types conform to both `Polygon`_and_`Show`.
160
+
We have explored possible encodings for such a feature but decided to remove them from this proposal, as support for multiple type classes can most likely be achieved without any additional language change.
128
161
129
-
Having alternatives is not a strict requirement for a proposal, but having at least one with carefully exposed pros and cons gives much more weight to the proposal as a whole.
162
+
Another open question relates to possible language support for shortening the expression of a container type and/or value.
130
163
131
164
## Related work
132
165
133
-
This section should list prior work related to the proposal, notably:
166
+
Swift support existential containers.
167
+
For instance, `largest` can be written as follows in Swift:
Unlike in this proposal, existential containers in Swift are built-in and have a dedicated syntax (i.e., `any P`).
176
+
One advantage of Swift's design is that the type system can treat an existential container as supertype of types conforming to that container's interface.
177
+
For example, `any Polygon` is supertype of `Square` (assuming the latter conforms to `Polygon`):
178
+
179
+
```swift
180
+
print(largest([Square(), Hexagon()]))
181
+
```
182
+
183
+
In contrast, to avoid possible undesirable complications, this proposal does not suggest any change to the subtyping relation of Scala.
184
+
185
+
Rust also supports existential containers in a similar way, writing `dyn P` to denote a container bundling some value of a type conforming to `P`.
186
+
Similar to Swift, existential containers in Rust are considered supertypes of the types conforming to their bound.
134
187
135
-
- A link to the Pre-SIP discussion that led to this proposal,
136
-
- Any other previous proposal (accepted or rejected) covering something similar as the current proposal,
137
-
- Whether the proposal is similar to something already existing in other languages,
138
-
- If there is already a proof-of-concept implementation, a link to it will be welcome here.
188
+
189
+
A more formal exploration of the state of the art as been documented in a research paper presented prior to this SIP [2].
139
190
140
191
## FAQ
141
192
142
-
This section will probably initially be empty. As discussions on the proposal progress, it is likely that some questions will come repeatedly. They should be listed here, with appropriate answers.
193
+
#### Is there any significant performance overhead in using existential containers?
194
+
195
+
On micro benchmarks testing method dispatch specifcally, we have measured that dispatching through existential containers in Scala was about twice as slow as traditional virtual method dispatch, which is explained by the extra pointer indirection introduced by an existential container.
196
+
This overhead drops below 10% on larger, more realistic benchmarks [2].
143
197
144
198
## References
145
199
146
200
1. Stefan Wehr and Peter Thiemann. 2011. JavaGI: The Interaction of Type Classes with Interfaces and Inheritance. ACM Transactions on Programming Languages and Systems 33, 4 (2011), 12:1–12:83. https://doi.org/10.1145/1985342.1985343
147
-
2.
201
+
2. Dimi Racordon and Eugene Flesselle and Matt Bovel. 2024. Existential Containers in Scala. ACM SIGPLAN International Conference on Managed Programming Languages and Runtimes, pp. 55-64. https://doi.org/10.1145/3679007.3685056
0 commit comments