@@ -109,43 +109,98 @@ More concretely, current Scala allows us to write parameterized type
109
109
definitions, abstract types, and type parameters. In the new scheme,
110
110
only classes (and traits) can have parameters and these are treated as
111
111
equivalent to type members. Type aliases and abstract types do not
112
- allow the definition of type members so we have to interprete
112
+ allow the definition of parameterized types so we have to interprete
113
113
polymorphic type aliases and abstract types specially.
114
114
115
- Modelling polymorphic type aliases
116
- ----------------------------------
115
+ Modelling polymorphic type aliases: simple case
116
+ -----------------------------------------------
117
117
118
118
A polymorphic type alias such as
119
119
120
- type Pair[T] = (T, T)
120
+ type Pair[T] = Tuple2[T, T]
121
+
122
+ where ` Tuple2 ` is declared as
123
+
124
+ class Tuple2[T1, T2] ...
125
+
126
+ is expanded to a monomorphic type alias like this:
127
+
128
+ type Pair = Tuple2 { type Tuple2$T2 = Tuple2$T1 }
129
+
130
+ More generally, each type parameter of the left-hand side must
131
+ appear as a type member of the right hand side type. Type members
132
+ must appear in the same order as their corresponding type parameters.
133
+ References to the type parameter are then translated to references to the
134
+ type member. The type member itself is left uninstantiated.
135
+
136
+ This technique can expand most polymorphic type aliases appearing
137
+ in Scala codebases but not all of them. For instance, the following
138
+ alias cannot be expanded, because the parameter type ` T ` is not a
139
+ type member of the right-hand side ` List[List[T]] ` .
140
+
141
+ type List2[T] = List[List[T]]
142
+
143
+ We scanned the Scala standard library for occurrences of polymorphic
144
+ type aliases and determined that only two occurrences could not be expanded.
145
+ In ` io/Codec.scala ` :
146
+
147
+ type Configure[T] = (T => T, Boolean)
148
+
149
+ And in ` collection/immutable/HashMap.scala ` :
150
+
151
+ private type MergeFunction[A1, B1] = ((A1, B1), (A1, B1)) => (A1, B1)
152
+
153
+ For these cases, we use a fall-back scheme that models a parameterized alias as a
154
+ ` Lambda ` type.
155
+
156
+ Modelling polymorphic type aliases: general case
157
+ ------------------------------------------------
158
+
159
+ A polymorphic type alias such as
160
+
161
+ type List2D[T] = List[List[T]]
121
162
122
163
is represented as a monomorphic type alias of a type lambda. Here's the expanded version of
123
164
the definition above:
124
165
125
- type Pair = Lambda1 { type Apply = (Arg1, Arg1) }
166
+ type List2D = Lambda$I { type Apply = List[List[$hkArg$0]] }
167
+
168
+ Here, ` Lambda$I ` is a standard trait defined as follows:
126
169
127
- Here, ` Lambda1 ` is a standard trait defined as follows:
170
+ trait Lambda$I[type $hkArg$0] { type +Apply }
128
171
129
- trait Lambda1[type Arg1, type Apply]
172
+ The ` I ` suffix of the ` Lambda ` trait indicates that it has one invariant type parameter (named $hkArg$0).
173
+ Other suffixes are ` P ` for covariant type parameters, and ` N ` for contravariant type parameters. Lambda traits can
174
+ have more than one type parameter. For instance, here is a trait with contravariant and covariant type parameters:
130
175
131
- According to our definitions of type parameters, ` Lambda1 ` has two type parameters
132
- and ` Pair ` has one.
176
+ trait Lambda$NP[type -$hkArg$0, +$hkArg1] { type +Apply } extends Lambda$IP with Lambda$PI
133
177
134
- There are ` LambdaN ` traits for higher arities as well. ` Lambda ` traits are special in that
178
+ Aside: the ` + ` prefix in front of ` Apply ` indicates that ` Apply ` is a covariant type field. Dotty
179
+ admits variance annotations on type members).
180
+
181
+ The definition of ` Lambda$NP ` shows that ` Lambda ` traits form a subtyping hierarchy: Traits which
182
+ have covariant or contravariant type parameters are subtypes of traits which don't. The supertraits
183
+ of ` Lambda$NP ` would themselves be written as follows.
184
+
185
+ trait Lambda$IP[type $hkArg$0, +$hkArg1] { type +Apply } extends Lambda$II
186
+ trait Lambda$NI[type -$hkArg$0, $hkArg1] { type +Apply } extends Lambda$II
187
+ trait Lambda$II[type -hkArg$0, $hkArg1] { type +Apply }
188
+
189
+ ` Lambda ` traits are special in that
135
190
they influence how type applications are expanded: If the standard type application ` T[X1, ..., Xn] `
136
191
leads to a subtype ` S ` of a type instance
137
192
138
- LambdaN { type Arg1 = T1; ...; type ArgN = Tn; type Apply ... }
193
+ LambdaXYZ { type Arg1 = T1; ...; type ArgN = Tn; type Apply ... }
139
194
140
195
where all argument fields ` Arg1, ..., ArgN ` are concretely defined
141
196
and the definition of the ` Apply ` field may be either abstract or concrete, then the application
142
197
is further expanded to ` S # Apply ` .
143
198
144
- For instance, the type instance ` Pair [String]` would be expanded to
199
+ For instance, the type instance ` List2D [String]` would be expanded to
145
200
146
- Lambda1 { type Arg1 = String; type Apply = (Arg1, Arg1) } # Apply
201
+ Lambda$I { type $hkArg$0 = String; type Apply = List[List[String]] } # Apply
147
202
148
- which in turn equals ` ( String, String) ` .
203
+ which in turn simplifies to ` List[List[ String]] ` .
149
204
150
205
2nd Example: Consider the two aliases
151
206
@@ -154,24 +209,24 @@ which in turn equals `(String, String)`.
154
209
155
210
These expand as follows:
156
211
157
- type RMap = Lambda2 { self1 => type Apply = Map[self1.Arg2 , self1.Arg1 ] }
158
- type RRMap = Lambda2 { self2 => type Apply = RMap[self2.Arg2 , self2.Arg1 ] }
212
+ type RMap = Lambda$II { self1 => type Apply = Map[self1.$hkArg$1 , self1.$hkArg$0 ] }
213
+ type RRMap = Lambda$II { self2 => type Apply = RMap[self2.$hkArg$1 , self2.$hkArg$0 ] }
159
214
160
215
Substituting the definition of ` RMap ` and expanding the type application gives:
161
216
162
- type RRMap = Lambda2 { self2 => type Apply =
163
- Lambda2 { self1 => type Apply = Map[self1.Arg2 , self1.Arg1 ] }
164
- { type Arg1 = self2.Arg2 ; type Arg2 = self2.Arg1 } # Apply }
217
+ type RRMap = Lambda$II { self2 => type Apply =
218
+ Lambda$II { self1 => type Apply = Map[self1.$hkArg$1 , self1.$hkArg$0 ] }
219
+ { type $hkArg$0 = self2.$hkArg$1 ; type $hkArg$1 = self2.$hkArg$0 } # Apply }
165
220
166
- Substituting the definitions ` self1.Arg {1,2} ` gives:
221
+ Substituting the definitions for ` self1.$hkArg$ {1,2} ` gives:
167
222
168
- type RRMap = Lambda2 { self2 => type Apply =
169
- Lambda2 { self1 => type Apply = Map[self2.Arg1 , self2.Arg2 ] }
170
- { type Arg1 = self2.Arg2 ; type Arg2 = self2.Arg1 } # Apply }
223
+ type RRMap = Lambda$II { self2 => type Apply =
224
+ Lambda$II { self1 => type Apply = Map[self2.$hkArg$0 , self2.$hkArg$1 ] }
225
+ { type $hkArg$0 = self2.$hkArg$1 ; type $hkArg$1 = self2.$hkArg$0 } # Apply }
171
226
172
227
Simplifiying the ` # Apply ` selection gives:
173
228
174
- type RRMap = Lambda2 { self2 => type Apply = Map[self2.Arg1 , self2.Arg2 ] }
229
+ type RRMap = Lambda$II { self2 => type Apply = Map[self2.$hkArg$0 , self2.$hkArg$1 ] }
175
230
176
231
This can be regarded as the eta-expanded version of ` Map ` . It has the same expansion as
177
232
@@ -188,33 +243,33 @@ Consider the higher-kinded type declaration
188
243
189
244
We expand this to
190
245
191
- type Rep <: Lambda1
246
+ type Rep <: Lambda$I
192
247
193
248
The type parameters of ` Rep ` are the type parameters of its upper bound, so
194
249
` Rep ` is a unary type constructor.
195
250
196
251
More generally, a higher-kinded type declaration
197
252
198
- type T[X1 >: S1 <: U1, ..., XN >: S1 <: UN] >: SR <: UR
253
+ type T[v1 X1 >: S1 <: U1, ..., vn XN >: S1 <: UN] >: SR <: UR
199
254
200
255
is encoded as
201
256
202
- type T <: LambdaN { self =>
203
- type Arg1 >: s(S1) <: s(U1)
257
+ type T <: LambdaV1...Vn { self =>
258
+ type v1 $hkArg$0 >: s(S1) <: s(U1)
204
259
...
205
- type ArgN >: s(SN) <: s(UN)
260
+ type vn $hkArg$N >: s(SN) <: s(UN)
206
261
type Apply >: s(SR) <: s(UR)
207
262
}
208
263
209
- where ` s ` is the substitution ` [XI := self.ArgI | I = 1,...,N] ` .
264
+ where ` s ` is the substitution ` [XI := self.$hkArg$I | I = 1,...,N] ` .
210
265
211
266
If we instantiate ` Rep ` with a type argument, this is expanded as was explained before.
212
267
213
268
Rep[String]
214
269
215
270
would expand to
216
271
217
- Rep { type Arg1 = String } # Apply
272
+ Rep { type $hkArg$0 = String } # Apply
218
273
219
274
If we instantiate the higher-kinded type with a concrete type constructor (i.e. a parameterized
220
275
trait or class), we have to do one extra adaptation to make it work. The parameterized trait
@@ -224,15 +279,15 @@ or class has to be eta-expanded so that it comforms to the `Lambda` bound. For i
224
279
225
280
would expand to
226
281
227
- type Rep = Lambda1 { type Apply = Set[Arg1 ] }
282
+ type Rep = Lambda1 { type Apply = Set[$hkArg$0 ] }
228
283
229
284
Or,
230
285
231
286
type Rep = Map[String, _]
232
287
233
288
would expand to
234
289
235
- type Rep = Lambda1 { type Apply = Map[String, Arg1 ] }
290
+ type Rep = Lambda1 { type Apply = Map[String, $hkArg$0 ] }
236
291
237
292
238
293
Full example
@@ -247,13 +302,13 @@ Consider the higher-kinded `Functor` type class
247
302
This would be represented as follows:
248
303
249
304
class Functor[F <: Lambda1] {
250
- def map[A, B](f: A => B): F { type Arg1 = A } # Apply => F { type Arg1 = B } # Apply
305
+ def map[A, B](f: A => B): F { type $hkArg$0 = A } # Apply => F { type $hkArg$0 = B } # Apply
251
306
}
252
307
253
308
The type ` Functor[List] ` would be represented as follows
254
309
255
310
Functor {
256
- type F = Lambda1 { type Apply = List[Arg1 ] }
311
+ type F = Lambda1 { type Apply = List[$hkArg$0 ] }
257
312
}
258
313
259
314
Now, assume we have a value
@@ -262,13 +317,13 @@ Now, assume we have a value
262
317
263
318
Then ` ml.map ` would have type
264
319
265
- s(F { type Arg1 = A } # Apply => F { type Arg1 = B } # Apply)
320
+ s(F { type $hkArg$0 = A } # Apply => F { type $hkArg$0 = B } # Apply)
266
321
267
- where ` s ` is the substitution of ` [F := Lambda1 { type Apply = List[Arg1 ] }] ` .
322
+ where ` s ` is the substitution of ` [F := Lambda1 { type Apply = List[$hkArg$0 ] }] ` .
268
323
This gives:
269
324
270
- Lambda1 { type Apply = List[Arg1 ] } { type Arg1 = A } # Apply
271
- => Lambda1 { type Apply = List[Arg1 ] } { type Arg1 = B } # Apply
325
+ Lambda1 { type Apply = List[$hkArg$0 ] } { type $hkArg$0 = A } # Apply
326
+ => Lambda1 { type Apply = List[$hkArg$0 ] } { type $hkArg$0 = B } # Apply
272
327
273
328
This type simplifies to:
274
329
@@ -301,21 +356,15 @@ Assuming that `Sub1#Inner = Sub2#Inner` could then lead to a soundness hole. To
301
356
problems, the types in ` X#Y ` were restricted so that ` Y ` was (an alias of) a class type and
302
357
` X ` was (an alias of) a class type with no abstract type members.
303
358
304
- I believe we can drop this restriction and allow arbitrary type projects ` X#Y ` if we
305
- are more careful with the subtyping rules. Specifically:
306
-
307
- A # X <: B # X
308
-
309
- if either ` A =:= B ` (i.e. ` A <: B ` and ` B <: A ` ), or the following three conditions hold:
359
+ I believe we can go back to regarding ` T#X ` as a fundamental type constructor, the way it
360
+ was done in pre-existential Scala, but with the following relaxed restriction:
310
361
311
- 1 . ` X ` is (an alias of) a class type,
312
- 2 . ` B ` is (an alias of) a class type without abstract type members.
313
- 3 . ` A <: B ` .
362
+ _In a type selection `T#x`, `T` is not allowed to have any abstract members different from `X`._
314
363
315
- In essence, we allow abstract types ` X ` , ` Y ` in a projection ` X#Y ` but we prevent in this
316
- case hiding conflicting type information in a subtype widening .
364
+ This would typecheck the higher-kinded types examples, because they only project with ` # Apply ` once all
365
+ ` $hkArg$ ` type members are fully instantiated .
317
366
318
- It would be good to study these rules formally, trying to verify their soundness.
367
+ It would be good to study this rule formally, trying to verify its soundness.
319
368
320
369
321
370
0 commit comments