aboutsummaryrefslogtreecommitdiff
path: root/docs/docs/internals/higher-kinded-v2.md
blob: 3019e3031426b64c4a51243790cb262112519977 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
---
layout: default
title: "Higher-Kinded Types in Dotty"
---

**This page is out of date and preserved for posterity. Please see [Implementing
Higher-Kinded Types in
Dotty](http://guillaume.martres.me/publications/dotty-hk.pdf) for a more up to
date version**

Higher-Kinded Types in Dotty V2
===============================
This note outlines how we intend to represent higher-kinded types in Dotty.
The principal idea is to collapse the four previously disparate features of
refinements, type parameters, existentials and higher-kinded types into just
one: refinements of type members. All other features will be encoded using
these refinements.

The complexity of type systems tends to grow exponentially with the number of
independent features, because there are an exponential number of possible
feature interactions. Consequently, a reduction from 4 to 1 fundamental
features achieves a dramatic reduction of complexity. It also adds some nice
usablilty improvements, notably in the area of partial type application.

This is a second version of the scheme which differs in a key aspect from the
first one: Following Adriaan's idea, we use traits with type members to model
type lambdas and type applications. This is both more general and more robust
than the intersections with type constructor traits that we had in the first
version.

The duality
-----------
The core idea: A parameterized class such as

```scala
class Map[K, V]
```

is treated as equivalent to a type with type members:

```scala
class Map { type Map$K; type Map$V }
```

The type members are name-mangled (i.e. `Map$K`) so that they do not conflict
with other members or parameters named `K` or `V`.

A type-instance such as `Map[String, Int]` would then be treated as equivalent
to:

```scala
Map { type Map$K = String; type Map$V = Int }
```

Named type parameters
---------------------
Type parameters can have unmangled names. This is achieved by adding the `type`
keyword to a type parameter declaration, analogous to how `val` indicates a
named field. For instance,

```scala
class Map[type K, type V]
```

is treated as equivalent to

```scala
class Map { type K; type V }
```

The parameters are made visible as fields.

Wildcards
---------
A wildcard type such as `Map[_, Int]` is equivalent to:

```scala
Map { type Map$V = Int }
```

I.e. `_`'s omit parameters from being instantiated. Wildcard arguments can have
bounds. E.g.

```scala
Map[_ <: AnyRef, Int]
```

is equivalent to:

```scala
Map { type Map$K <: AnyRef; type Map$V = Int }
```

Type parameters in the encodings
--------------------------------
The notion of type parameters makes sense even for encoded types, which do not
contain parameter lists in their syntax. Specifically, the type parameters of a
type are a sequence of type fields that correspond to parameters in the
unencoded type. They are determined as follows.

* The type parameters of a class or trait type are those parameter fields declared in the class
  that are not yet instantiated, in the order they are given. Type parameter fields of parents
  are not considered.
* The type parameters of an abstract type are the type parameters of its upper bound.
* The type parameters of an alias type are the type parameters of its right hand side.
* The type parameters of every other type is the empty sequence.

Partial applications
--------------------
The definition of type parameters in the previous section leads to a simple
model of partial applications.  Consider for instance:

```scala
type Histogram = Map[_, Int]
```

`Histogram` is a higher-kinded type that still has one type parameter.
`Histogram[String]` would be a possible type instance, and it would be
equivalent to `Map[String, Int]`.


Modelling polymorphic type declarations
---------------------------------------
The partial application scheme gives us a new -- and quite elegant -- way to do
certain higher-kinded types. But how do we interprete the poymorphic types that
exist in current Scala?

More concretely, current Scala allows us to write parameterized type
definitions, abstract types, and type parameters. In the new scheme, only
classes (and traits) can have parameters and these are treated as equivalent to
type members. Type aliases and abstract types do not allow the definition of
parameterized types so we have to interprete polymorphic type aliases and
abstract types specially.

Modelling polymorphic type aliases: simple case
-----------------------------------------------
A polymorphic type alias such as:

```scala
type Pair[T] = Tuple2[T, T]
```

where `Tuple2` is declared as

```scala
class Tuple2[T1, T2] ...
```

is expanded to a monomorphic type alias like this:

```scala
type Pair = Tuple2 { type Tuple2$T2 = Tuple2$T1 }
```

More generally, each type parameter of the left-hand side must appear as a type
member of the right hand side type. Type members must appear in the same order
as their corresponding type parameters.  References to the type parameter are
then translated to references to the type member. The type member itself is
left uninstantiated.

This technique can expand most polymorphic type aliases appearing in Scala
codebases but not all of them. For instance, the following alias cannot be
expanded, because the parameter type `T` is not a type member of the right-hand
side `List[List[T]]`.

```scala
type List2[T] = List[List[T]]
```

We scanned the Scala standard library for occurrences of polymorphic type
aliases and determined that only two occurrences could not be expanded.  In
`io/Codec.scala`:

```scala
type Configure[T] = (T => T, Boolean)
```

And in `collection/immutable/HashMap.scala`:

```scala
private type MergeFunction[A1, B1] = ((A1, B1), (A1, B1)) => (A1, B1)
```

For these cases, we use a fall-back scheme that models a parameterized alias as
a `Lambda` type.

Modelling polymorphic type aliases: general case
------------------------------------------------
A polymorphic type alias such as:

```scala
type List2D[T] = List[List[T]]
```

is represented as a monomorphic type alias of a type lambda. Here's the
expanded version of the definition above:

```scala
type List2D = Lambda$I { type Apply = List[List[$hkArg$0]] }
```

Here, `Lambda$I` is a standard trait defined as follows:

```scala
trait Lambda$I[type $hkArg$0] { type +Apply }
```

The `I` suffix of the `Lambda` trait indicates that it has one invariant type
parameter (named $hkArg$0).  Other suffixes are `P` for covariant type
parameters, and `N` for contravariant type parameters. Lambda traits can have
more than one type parameter. For instance, here is a trait with contravariant
and covariant type parameters:

```scala
trait Lambda$NP[type -$hkArg$0, +$hkArg1] { type +Apply } extends Lambda$IP with Lambda$NI
```

Aside: the `+` prefix in front of `Apply` indicates that `Apply` is a covariant
type field. Dotty admits variance annotations on type members.

The definition of `Lambda$NP` shows that `Lambda` traits form a subtyping
hierarchy: Traits which have covariant or contravariant type parameters are
subtypes of traits which don't. The supertraits of `Lambda$NP` would themselves
be written as follows.

```scala
trait Lambda$IP[type $hkArg$0, +$hkArg1] { type +Apply } extends Lambda$II
trait Lambda$NI[type -$hkArg$0, $hkArg1] { type +Apply } extends Lambda$II
trait Lambda$II[type $hkArg$0, $hkArg1] { type +Apply }
```

`Lambda` traits are special in that they influence how type applications are
expanded: If the standard type application `T[X1, ..., Xn]` leads to a subtype
`S` of a type instance

```scala
LambdaXYZ { type Arg1 = T1; ...; type ArgN = Tn; type Apply ... }
```

where all argument fields `Arg1, ..., ArgN` are concretely defined and the
definition of the `Apply` field may be either abstract or concrete, then the
application is further expanded to `S # Apply`.

For instance, the type instance `List2D[String]` would be expanded to

```scala
Lambda$I { type $hkArg$0 = String; type Apply = List[List[String]] } # Apply
```

which in turn simplifies to `List[List[String]]`.

2nd Example: Consider the two aliases

```scala
type RMap[K, V] = Map[V, K]
type RRMap[K, V] = RMap[V, K]
```

These expand as follows:

```scala
type RMap  = Lambda$II { self1 => type Apply = Map[self1.$hkArg$1, self1.$hkArg$0] }
type RRMap = Lambda$II { self2 => type Apply = RMap[self2.$hkArg$1, self2.$hkArg$0] }
```

Substituting the definition of `RMap` and expanding the type application gives:

```scala
type RRMap = Lambda$II { self2 => type Apply =
               Lambda$II { self1 => type Apply = Map[self1.$hkArg$1, self1.$hkArg$0] }
                 { type $hkArg$0 = self2.$hkArg$1; type $hkArg$1 = self2.$hkArg$0 } # Apply }
```

Substituting the definitions for `self1.$hkArg${1,2}` gives:

```scala
type RRMap = Lambda$II { self2 => type Apply =
               Lambda$II { self1 => type Apply = Map[self2.$hkArg$0, self2.$hkArg$1] }
                  { type $hkArg$0 = self2.$hkArg$1; type $hkArg$1 = self2.$hkArg$0 } # Apply }
```

Simplifiying the `# Apply` selection gives:

```scala
type RRMap = Lambda$II { self2 => type Apply = Map[self2.$hkArg$0, self2.$hkArg$1] }
```

This can be regarded as the eta-expanded version of `Map`. It has the same expansion as

```scala
type IMap[K, V] = Map[K, V]
```

Modelling higher-kinded types
-----------------------------
The encoding of higher-kinded types uses again the `Lambda` traits to represent
type constructors. Consider the higher-kinded type declaration

```scala
type Rep[T]
```

We expand this to

```scala
type Rep <: Lambda$I
```

The type parameters of `Rep` are the type parameters of its upper bound, so
`Rep` is a unary type constructor.

More generally, a higher-kinded type declaration

```scala
type T[v1 X1 >: S1 <: U1, ..., vn XN >: S1 <: UN] >: SR <: UR
```

is encoded as

```scala
type T <: LambdaV1...Vn { self =>
  type v1 $hkArg$0 >: s(S1) <: s(U1)
  ...
  type vn $hkArg$N >: s(SN) <: s(UN)
  type Apply >: s(SR) <: s(UR)
}
```

where `s` is the substitution `[XI := self.$hkArg$I | I = 1,...,N]`.

If we instantiate `Rep` with a type argument, this is expanded as was explained
before.

```scala
Rep[String]
```

would expand to

```scala
Rep { type $hkArg$0 = String } # Apply
```

If we instantiate the higher-kinded type with a concrete type constructor (i.e.
a parameterized trait or class), we have to do one extra adaptation to make it
work. The parameterized trait or class has to be eta-expanded so that it
comforms to the `Lambda` bound. For instance,

```scala
type Rep = Set
```

would expand to:

```scala
type Rep = Lambda1 { type Apply = Set[$hkArg$0] }
```

Or,

```scala
type Rep = Map[String, _]
```

would expand to

```scala
type Rep = Lambda1 { type Apply = Map[String, $hkArg$0] }
```

Full example
------------
Consider the higher-kinded `Functor` type class

```scala
class Functor[F[_]] {
   def map[A, B](f: A => B): F[A] => F[B]
}
```

This would be represented as follows:

```scala
class Functor[F <: Lambda1] {
   def map[A, B](f: A => B): F { type $hkArg$0 = A } # Apply  =>  F { type $hkArg$0 = B } # Apply
}
```

The type `Functor[List]` would be represented as follows

```scala
Functor {
   type F = Lambda1 { type Apply = List[$hkArg$0] }
}
```

Now, assume we have a value

```scala
val ml: Functor[List]
```

Then `ml.map` would have type

```scala
s(F { type $hkArg$0 = A } # Apply  =>  F { type $hkArg$0 = B } # Apply)
```

where `s` is the substitution of `[F := Lambda1 { type Apply = List[$hkArg$0] }]`.
This gives:

```scala
Lambda1 { type Apply = List[$hkArg$0] } { type $hkArg$0 = A } # Apply
 =>  Lambda1 { type Apply = List[$hkArg$0] } { type $hkArg$0 = B } # Apply
```

This type simplifies to:

```scala
List[A] => List[B]
```

Status of `#`
-------------
In the scheme above we have silently assumed that `#` "does the right thing",
i.e. that the types are well-formed and we can collapse a type alias with a `#`
projection, thereby giving us a form of beta reduction.

In Scala 2.x, this would not work, because `T#X` means `x.X forSome { val x: T
}`. Hence, two occurrences of `Rep[Int]` say, would not be recognized to be
equal because the existential would be opened each time afresh.

In pre-existentials Scala, this would not have worked either. There, `T#X` was
a fundamental type constructor, but was restricted to alias types or classes
for both `T` and `X`.  Roughly, `#` was meant to encode Java's inner classes.
In Java, given the classes

```scala
class Outer { class Inner }
class Sub1 extends Outer
class Sub2 extends Outer
```

The types `Outer#Inner`, `Sub1#Inner` and `Sub2#Inner` would all exist and be
regarded as equal to each other. But if `Outer` had abstract type members this
would not work, since an abstract type member could be instantiated differently
in `Sub1` and `Sub2`.  Assuming that `Sub1#Inner = Sub2#Inner` could then lead
to a soundness hole. To avoid soundness problems, the types in `X#Y` were
restricted so that `Y` was (an alias of) a class type and `X` was (an alias of)
a class type with no abstract type members.

I believe we can go back to regarding `T#X` as a fundamental type constructor,
the way it was done in pre-existential Scala, but with the following relaxed
restriction:

> In a type selection `T#x`, `T` is not allowed to have any abstract members different from `X`

This would typecheck the higher-kinded types examples, because they only
project with `# Apply` once all `$hkArg$` type members are fully instantiated.

It would be good to study this rule formally, trying to verify its soundness.