Skip to content

Commit 0d77aae

Browse files
committed
Sync with implementation
Adapted docs so that they reflect what has been implemented.
1 parent 41e9201 commit 0d77aae

File tree

1 file changed

+100
-51
lines changed

1 file changed

+100
-51
lines changed

docs/HigherKinded-v2.md

+100-51
Original file line numberDiff line numberDiff line change
@@ -109,43 +109,98 @@ More concretely, current Scala allows us to write parameterized type
109109
definitions, abstract types, and type parameters. In the new scheme,
110110
only classes (and traits) can have parameters and these are treated as
111111
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
113113
polymorphic type aliases and abstract types specially.
114114

115-
Modelling polymorphic type aliases
116-
----------------------------------
115+
Modelling polymorphic type aliases: simple case
116+
-----------------------------------------------
117117

118118
A polymorphic type alias such as
119119

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]]
121162

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

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:
126169

127-
Here, `Lambda1` is a standard trait defined as follows:
170+
trait Lambda$I[type $hkArg$0] { type +Apply }
128171

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:
130175

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
133177

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
135190
they influence how type applications are expanded: If the standard type application `T[X1, ..., Xn]`
136191
leads to a subtype `S` of a type instance
137192

138-
LambdaN { type Arg1 = T1; ...; type ArgN = Tn; type Apply ... }
193+
LambdaXYZ { type Arg1 = T1; ...; type ArgN = Tn; type Apply ... }
139194

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

144-
For instance, the type instance `Pair[String]` would be expanded to
199+
For instance, the type instance `List2D[String]` would be expanded to
145200

146-
Lambda1 { type Arg1 = String; type Apply = (Arg1, Arg1) } # Apply
201+
Lambda$I { type $hkArg$0 = String; type Apply = List[List[String]] } # Apply
147202

148-
which in turn equals `(String, String)`.
203+
which in turn simplifies to `List[List[String]]`.
149204

150205
2nd Example: Consider the two aliases
151206

@@ -154,24 +209,24 @@ which in turn equals `(String, String)`.
154209

155210
These expand as follows:
156211

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] }
159214

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

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 }
165220

166-
Substituting the definitions `self1.Arg{1,2}` gives:
221+
Substituting the definitions for `self1.$hkArg${1,2}` gives:
167222

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 }
171226

172227
Simplifiying the `# Apply` selection gives:
173228

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] }
175230

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

@@ -188,33 +243,33 @@ Consider the higher-kinded type declaration
188243

189244
We expand this to
190245

191-
type Rep <: Lambda1
246+
type Rep <: Lambda$I
192247

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

196251
More generally, a higher-kinded type declaration
197252

198-
type T[X1 >: S1 <: U1, ..., XN >: S1 <: UN] >: SR <: UR
253+
type T[v1 X1 >: S1 <: U1, ..., vn XN >: S1 <: UN] >: SR <: UR
199254

200255
is encoded as
201256

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)
204259
...
205-
type ArgN >: s(SN) <: s(UN)
260+
type vn $hkArg$N >: s(SN) <: s(UN)
206261
type Apply >: s(SR) <: s(UR)
207262
}
208263

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]`.
210265

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

213268
Rep[String]
214269

215270
would expand to
216271

217-
Rep { type Arg1 = String } # Apply
272+
Rep { type $hkArg$0 = String } # Apply
218273

219274
If we instantiate the higher-kinded type with a concrete type constructor (i.e. a parameterized
220275
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
224279

225280
would expand to
226281

227-
type Rep = Lambda1 { type Apply = Set[Arg1] }
282+
type Rep = Lambda1 { type Apply = Set[$hkArg$0] }
228283

229284
Or,
230285

231286
type Rep = Map[String, _]
232287

233288
would expand to
234289

235-
type Rep = Lambda1 { type Apply = Map[String, Arg1] }
290+
type Rep = Lambda1 { type Apply = Map[String, $hkArg$0] }
236291

237292

238293
Full example
@@ -247,13 +302,13 @@ Consider the higher-kinded `Functor` type class
247302
This would be represented as follows:
248303

249304
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
251306
}
252307

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

255310
Functor {
256-
type F = Lambda1 { type Apply = List[Arg1] }
311+
type F = Lambda1 { type Apply = List[$hkArg$0] }
257312
}
258313

259314
Now, assume we have a value
@@ -262,13 +317,13 @@ Now, assume we have a value
262317

263318
Then `ml.map` would have type
264319

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)
266321

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] }]`.
268323
This gives:
269324

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
272327

273328
This type simplifies to:
274329

@@ -301,21 +356,15 @@ Assuming that `Sub1#Inner = Sub2#Inner` could then lead to a soundness hole. To
301356
problems, the types in `X#Y` were restricted so that `Y` was (an alias of) a class type and
302357
`X` was (an alias of) a class type with no abstract type members.
303358

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:
310361

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`._
314363

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.
317366

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.
319368

320369

321370

0 commit comments

Comments
 (0)