Skip to content

Commit e73fab3

Browse files
committed
Merge pull request #135 from dotty-staging/docs/higher-kinded-v2
Docs/higher kinded v2
2 parents 1c4e9c2 + 0d77aae commit e73fab3

File tree

1 file changed

+375
-0
lines changed

1 file changed

+375
-0
lines changed

docs/HigherKinded-v2.md

Lines changed: 375 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,375 @@
1+
Higher-Kinded Types in Dotty V2
2+
===============================
3+
4+
This note outlines how we intend to represent higher-kinded types in
5+
Dotty. The principal idea is to collapse the four previously
6+
disparate features of refinements, type parameters, existentials and
7+
higher-kinded types into just one: refinements of type members. All
8+
other features will be encoded using these refinements.
9+
10+
The complexity of type systems tends to grow exponentially with the
11+
number of independent features, because there are an exponential
12+
number of possible feature interactions. Consequently, a reduction
13+
from 4 to 1 fundamental features achieves a dramatic reduction of
14+
complexity. It also adds some nice usablilty improvements, notably in
15+
the area of partial type application.
16+
17+
This is a second version of the scheme which differs in a key aspect
18+
from the first one: Following Adriaan's idea, we use traits with type
19+
members to model type lambdas and type applications. This is both more
20+
general and more robust than the intersections with type constructor
21+
traits that we had in the first version.
22+
23+
The duality
24+
-----------
25+
26+
The core idea: A parameterized class such as
27+
28+
class Map[K, V]
29+
30+
is treated as equivalent to a type with type members:
31+
32+
class Map { type Map$K; type Map$V }
33+
34+
The type members are name-mangled (i.e. `Map$K`) so that they do not conflict with other
35+
members or parameters named `K` or `V`.
36+
37+
A type-instance such as `Map[String, Int]` would then be treated as equivalent to
38+
39+
Map { type Map$K = String; type Map$V = Int }
40+
41+
Named type parameters
42+
---------------------
43+
44+
Type parameters can have unmangled names. This is achieved by adding the `type` keyword
45+
to a type parameter declaration, analogous to how `val` indicates a named field. For instance,
46+
47+
class Map[type K, type V]
48+
49+
is treated as equivalent to
50+
51+
class Map { type K; type V }
52+
53+
The parameters are made visible as fields.
54+
55+
Wildcards
56+
---------
57+
58+
A wildcard type such as `Map[_, Int]` is equivalent to
59+
60+
Map { type Map$V = Int }
61+
62+
I.e. `_`'s omit parameters from being instantiated. Wildcard arguments
63+
can have bounds. E.g.
64+
65+
Map[_ <: AnyRef, Int]
66+
67+
is equivalent to
68+
69+
Map { type Map$K <: AnyRef; type Map$V = Int }
70+
71+
72+
Type parameters in the encodings
73+
--------------------------------
74+
75+
The notion of type parameters makes sense even for encoded types,
76+
which do not contain parameter lists in their syntax. Specifically,
77+
the type parameters of a type are a sequence of type fields that
78+
correspond to paraneters in the unencoded type. They are determined as
79+
follows.
80+
81+
- The type parameters of a class or trait type are those parameter fields declared in the class
82+
that are not yet instantiated, in the order they are given. Type parameter fields of parents
83+
are not considered.
84+
- The type parameters of an abstract type are the type parameters of its upper bound.
85+
- The type parameters of an alias type are the type parameters of its right hand side.
86+
- The type parameters of every other type is the empty sequence.
87+
88+
Partial applications
89+
--------------------
90+
91+
The definition of type parameters in the previous section leads to a simple model of partial applications.
92+
Consider for instance:
93+
94+
type Histogram = Map[_, Int]
95+
96+
`Histogram` is a higher-kinded type that still has one type parameter.
97+
`Histogram[String]`
98+
would be a possible type instance, and it would be equivalent to `Map[String, Int]`.
99+
100+
101+
Modelling polymorphic type declarations
102+
---------------------------------------
103+
104+
The partial application scheme gives us a new -- and quite elegant --
105+
way to do certain higher-kinded types. But how do we interprete the
106+
poymorphic types that exist in current Scala?
107+
108+
More concretely, current Scala allows us to write parameterized type
109+
definitions, abstract types, and type parameters. In the new scheme,
110+
only classes (and traits) can have parameters and these are treated as
111+
equivalent to type members. Type aliases and abstract types do not
112+
allow the definition of parameterized types so we have to interprete
113+
polymorphic type aliases and abstract types specially.
114+
115+
Modelling polymorphic type aliases: simple case
116+
-----------------------------------------------
117+
118+
A polymorphic type alias such as
119+
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]]
162+
163+
is represented as a monomorphic type alias of a type lambda. Here's the expanded version of
164+
the definition above:
165+
166+
type List2D = Lambda$I { type Apply = List[List[$hkArg$0]] }
167+
168+
Here, `Lambda$I` is a standard trait defined as follows:
169+
170+
trait Lambda$I[type $hkArg$0] { type +Apply }
171+
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:
175+
176+
trait Lambda$NP[type -$hkArg$0, +$hkArg1] { type +Apply } extends Lambda$IP with Lambda$PI
177+
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
190+
they influence how type applications are expanded: If the standard type application `T[X1, ..., Xn]`
191+
leads to a subtype `S` of a type instance
192+
193+
LambdaXYZ { type Arg1 = T1; ...; type ArgN = Tn; type Apply ... }
194+
195+
where all argument fields `Arg1, ..., ArgN` are concretely defined
196+
and the definition of the `Apply` field may be either abstract or concrete, then the application
197+
is further expanded to `S # Apply`.
198+
199+
For instance, the type instance `List2D[String]` would be expanded to
200+
201+
Lambda$I { type $hkArg$0 = String; type Apply = List[List[String]] } # Apply
202+
203+
which in turn simplifies to `List[List[String]]`.
204+
205+
2nd Example: Consider the two aliases
206+
207+
type RMap[K, V] = Map[V, K]]
208+
type RRMap[K, V] = RMap[V, K]
209+
210+
These expand as follows:
211+
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] }
214+
215+
Substituting the definition of `RMap` and expanding the type application gives:
216+
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 }
220+
221+
Substituting the definitions for `self1.$hkArg${1,2}` gives:
222+
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 }
226+
227+
Simplifiying the `# Apply` selection gives:
228+
229+
type RRMap = Lambda$II { self2 => type Apply = Map[self2.$hkArg$0, self2.$hkArg$1] }
230+
231+
This can be regarded as the eta-expanded version of `Map`. It has the same expansion as
232+
233+
type IMap[K, V] = Map[K, V]
234+
235+
236+
Modelling higher-kinded types
237+
-----------------------------
238+
239+
The encoding of higher-kinded types uses again the `Lambda` traits to represent type constructors.
240+
Consider the higher-kinded type declaration
241+
242+
type Rep[T]
243+
244+
We expand this to
245+
246+
type Rep <: Lambda$I
247+
248+
The type parameters of `Rep` are the type parameters of its upper bound, so
249+
`Rep` is a unary type constructor.
250+
251+
More generally, a higher-kinded type declaration
252+
253+
type T[v1 X1 >: S1 <: U1, ..., vn XN >: S1 <: UN] >: SR <: UR
254+
255+
is encoded as
256+
257+
type T <: LambdaV1...Vn { self =>
258+
type v1 $hkArg$0 >: s(S1) <: s(U1)
259+
...
260+
type vn $hkArg$N >: s(SN) <: s(UN)
261+
type Apply >: s(SR) <: s(UR)
262+
}
263+
264+
where `s` is the substitution `[XI := self.$hkArg$I | I = 1,...,N]`.
265+
266+
If we instantiate `Rep` with a type argument, this is expanded as was explained before.
267+
268+
Rep[String]
269+
270+
would expand to
271+
272+
Rep { type $hkArg$0 = String } # Apply
273+
274+
If we instantiate the higher-kinded type with a concrete type constructor (i.e. a parameterized
275+
trait or class), we have to do one extra adaptation to make it work. The parameterized trait
276+
or class has to be eta-expanded so that it comforms to the `Lambda` bound. For instance,
277+
278+
type Rep = Set
279+
280+
would expand to
281+
282+
type Rep = Lambda1 { type Apply = Set[$hkArg$0] }
283+
284+
Or,
285+
286+
type Rep = Map[String, _]
287+
288+
would expand to
289+
290+
type Rep = Lambda1 { type Apply = Map[String, $hkArg$0] }
291+
292+
293+
Full example
294+
------------
295+
296+
Consider the higher-kinded `Functor` type class
297+
298+
class Functor[F[_]] {
299+
def map[A, B](f: A => B): F[A] => F[B]
300+
}
301+
302+
This would be represented as follows:
303+
304+
class Functor[F <: Lambda1] {
305+
def map[A, B](f: A => B): F { type $hkArg$0 = A } # Apply => F { type $hkArg$0 = B } # Apply
306+
}
307+
308+
The type `Functor[List]` would be represented as follows
309+
310+
Functor {
311+
type F = Lambda1 { type Apply = List[$hkArg$0] }
312+
}
313+
314+
Now, assume we have a value
315+
316+
val ml: Functor[List]
317+
318+
Then `ml.map` would have type
319+
320+
s(F { type $hkArg$0 = A } # Apply => F { type $hkArg$0 = B } # Apply)
321+
322+
where `s` is the substitution of `[F := Lambda1 { type Apply = List[$hkArg$0] }]`.
323+
This gives:
324+
325+
Lambda1 { type Apply = List[$hkArg$0] } { type $hkArg$0 = A } # Apply
326+
=> Lambda1 { type Apply = List[$hkArg$0] } { type $hkArg$0 = B } # Apply
327+
328+
This type simplifies to:
329+
330+
List[A] => List[B]
331+
332+
Status of #
333+
-----------
334+
335+
In the scheme above we have silently assumed that `#` "does the right
336+
thing", i.e. that the types are well-formed and we can collapse a type
337+
alias with a `#` projection, thereby giving us a form of beta
338+
reduction.
339+
340+
In Scala 2.x, this would not work, because `T#X` means `x.X forSome { val x: T }`.
341+
Hence, two occurrences of `Rep[Int]` say, would not be recognized to be equal because the
342+
existential would be opened each time afresh.
343+
344+
In pre-existentials Scala, this would not have worked either. There, `T#X` was a fundamental
345+
type constructor, but was restricted to alias types or classes for both `T` and `X`.
346+
Roughly, `#` was meant to encode Java's inner classes. In Java, given the classes
347+
348+
class Outer { class Inner }
349+
class Sub1 extends Outer
350+
class Sub2 extends Outer
351+
352+
The types `Outer#Inner`, `Sub1#Inner` and `Sub2#Inner` would all exist and be
353+
regarded as equal to each other. But if `Outer` had abstract type members this would
354+
not work, since an abstract type member could be instantiated differently in `Sub1` and `Sub2`.
355+
Assuming that `Sub1#Inner = Sub2#Inner` could then lead to a soundness hole. To avoid soundness
356+
problems, the types in `X#Y` were restricted so that `Y` was (an alias of) a class type and
357+
`X` was (an alias of) a class type with no abstract type members.
358+
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:
361+
362+
_In a type selection `T#x`, `T` is not allowed to have any abstract members different from `X`._
363+
364+
This would typecheck the higher-kinded types examples, because they only project with `# Apply` once all
365+
`$hkArg$` type members are fully instantiated.
366+
367+
It would be good to study this rule formally, trying to verify its soundness.
368+
369+
370+
371+
372+
373+
374+
375+

0 commit comments

Comments
 (0)