Skip to content

Commit 369008d

Browse files
committed
Description and informal spec for match types
This is largely the result of a discussion we had on Friday with Guillaume, Sandro, Georg and Olivier. The rules for typing match expressions have evolved a bit from the state we discussed there.
1 parent a7ea105 commit 369008d

File tree

2 files changed

+189
-0
lines changed

2 files changed

+189
-0
lines changed

docs/docs/reference/match-types.md

+187
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
---
2+
layout: doc-page
3+
title: "Match Types"
4+
---
5+
6+
A match type reduces to one of a number of right hand sides, depending on a scrutinee type. Example:
7+
8+
```scala
9+
type Elem[X] = X match {
10+
case String => Char
11+
case Array[t] => t
12+
case Iterable[t] => t
13+
}
14+
```
15+
This defines a type that, depending on the scrutinee type `X`, can reduce to one of its right hand sides. For instance,
16+
```scala
17+
Elem[String] =:= Char
18+
Elem[Array[Int]] =:= Int
19+
Elem[List[Float]] =:= Float
20+
Elem[Nil] =:= Nothing
21+
```
22+
Here `=:=` is understood to mean that left and right hand sides are mutatually subtypes of each other.
23+
24+
In general, a match type is of the form
25+
```scala
26+
S match { P1 => Tn ... Pn => Tn }
27+
```
28+
where `S`, `T1`, ..., `Tn` are types and `P1`, ..., `Pn` are type patterns. Type variables
29+
in patterns start as usual with a lower case letter.
30+
31+
Match types can form part of recursive type definitions. Example:
32+
```scala
33+
type LeafElem[X] = X match {
34+
case String => Char
35+
case Array[t] => LeafElem[t]
36+
case Iterable[t] => LeafElem[t]
37+
case t <: AnyVal => t
38+
}
39+
```
40+
Recursive match type definitions can also be given an upper bound, like this:
41+
```scala
42+
type Concat[+Xs <: Tuple, +Ys <: Tuple] <: Tuple = Xs match {
43+
case Unit => Ys
44+
case x *: xs => x *: Concat[xs, Ys]
45+
}
46+
```
47+
In this definition, every instance of `Concat[A, B]`, whether reducible or not, is known to be a subtype of `Tuple`. This is necessary to make the recursive invocation `x *: Concat[xs, Ys` type check, since `*:` demands a `Tuple` as its right operand.
48+
49+
## Representation of Match Types
50+
51+
The internal representation of a match type
52+
```
53+
S match { P1 => Tn ... Pn => Tn }
54+
```
55+
is `Match(S, C1, ..., Cn) <: B` where each case `Ci` is of the form
56+
```
57+
[Xs] => P => T
58+
```
59+
Here, `[Xs]` is a type parameter clause of the variables bound in pattern `Pi`. If there are no bound type variables in a case, the type parameter clause is omitted and only the function type `P => T` is kept. So each case is either a unary function type or a type lambda over a unary function type.
60+
61+
`B` is the declared upper bound of the match type, or `Any` if no such bound is given.
62+
We will leave it out in places where it does not matter for the discussion. Scrutinee, bound and pattern types must be first-order types.
63+
64+
## Match type reduction
65+
66+
We define match type reduction in terms of an auxiliary relation, `can-reduce`:
67+
68+
```
69+
Match(S, C1, ..., Cn) can-reduce i, T'
70+
```
71+
if `Ci = [Xs] => P => T` and there are minimal instantiations `Is` of the type variables `Xs` such that
72+
```
73+
S <: [Xs := Is] P
74+
T' = [Xs := Is] T
75+
```
76+
An instantiation `Is` is _minimal_ for `Xs` if all type variables in `Xs` that appear
77+
covariantly and nonvariantly in `Is` are as small as possible and all type variables in `Xs` that appear contravariantly in `Is` are as large as possible. Here, "small" and "large" are understood wrt `<:`.
78+
79+
For simplicity, we have omitted constraint handling so far. The full formulation of subtyping tests describes them as a function from a constraint and a pair of types to
80+
either _success_ and a new constraint or _failure_. In the context of reduction, the subtyping test `S <: [Xs := Is] P` is understood to leave the bounds of all variables
81+
in the input constraint unchcanged, i.e. existing variables in the constraint cannot be instantiated by matching the scrutinee against the patterns.
82+
83+
Using `can-reduce`, we can now define match type reduction proper in the `reduces-to` relation:
84+
```
85+
Match(S, C1, ..., Cn) reduces-to T
86+
```
87+
if `Ci_1, ..., Ci_k` is a maximal non-empty subset of `C1, ..., Cn` such that for each `i_j`:
88+
```
89+
Match(S, C1, ..., Cn) can-reduce i_j, Ti_j
90+
```
91+
and
92+
```
93+
T = Ti_1 & ... & Ti_k
94+
```
95+
In other words, a match reduces to the intersection of all right hand sides it can reduce to. This "parallel" notion of reduction was picked for its nice algebraic properties, even though it does not correspond directly to the operational semantics of pattern matching on terms, where the first matching case is chosen.
96+
97+
## Subtyping Rules for Match Types
98+
99+
The following rules apply to match types. For simplicity, we omit environments and constraints.
100+
101+
The first rule is a structural comparison between two match types:
102+
```
103+
Match(S, C1, ..., Cn) <: Match(T, D1, ..., Dm)
104+
```
105+
` `if
106+
```
107+
S <: T, m <= n, Ci <: Di for i in 1..n
108+
```
109+
I.e. scrutinees and corresponding cases must be subtypes, no case re-ordering is allowed, but the subtype can have more cases than the supertype.
110+
111+
The second rule states that a match type and its redux are mutual subtypes
112+
```
113+
Match(S, Cs) <: T
114+
T <: Match(S, Cs)
115+
```
116+
` `if
117+
```
118+
Match(S, Cs) reduces-to T
119+
```
120+
121+
The third rule states that a match type conforms to its upper bound
122+
```
123+
(Match(S, Cs) <: B) <: B
124+
```
125+
126+
## Variance Laws for Match Types
127+
128+
Within a match type `Match(S, Cs) <: B`, all occurrences of type variables count as covariant. By the nature of the cases `Ci` this means that occurrences in pattern position are contravarant (since patterns are represented as function type arguments).
129+
130+
## Typing Rules for Match Expressions
131+
132+
Typing rules for match expressions have to account for the difference between sequential match on the term level and parallel match on the type level. As a running example consider:
133+
```scala
134+
type M[X] = X match {
135+
case A => 1
136+
case B => 2
137+
}
138+
def m[X](x: X): M[X] = x match {
139+
case _: A => 1
140+
case _: B => 2
141+
}
142+
```
143+
As a first approximation, the typing rules for match expressions are as usual. E.g. to typecheck the first case `case _: A => 1` of the definition of `m` above, GADT matching will produce the constraint `X <: A`. Therefore, `M[X]` reduces to the singleton type `1`.
144+
The right hand side `1` of the case conforms to this type, so the case typechecks. Typechecking the second case proceeds similarly.
145+
146+
However, it turns out that these rules are not enough for type soundness. To see this, assume that `A` and `B` are traits that are both extended by a common class `C`. In this case, `M[C]` reduces to `1 & 2`, but `m(new C)` reduces to `1`. So the type of the application `m(new C)` does not match the reduced result type of `m`, which means soundness is violated.
147+
148+
To plug the soundness hole, we have to tighten the typing rules for match expressions. In the example above we need to also consider the case where the scrutinee type `X` is a subtype of `A` and `B`. In this case, the match expression still returns `1` but the match type `M[X]` reduces to `1 & 2`, which means there should be a type error. However, this second check can be omitted if `A` and `B` are types that don't overlap. We can omit the check because in that case there is no scrutinee value `x` that could reduce to `1`, so no discrepancy can arise at runtime.
149+
150+
More generally, we proceeed as follows:
151+
152+
When typechecking the `i`th case of a match expression
153+
```
154+
t match { case P_1 => t_1 ... case P_n => t_n
155+
```
156+
where `t` has type `T` and `t_i` has type `T_i`
157+
against an expected match type `R`:
158+
159+
1. Determine all maximal sequences of
160+
patterns `P_j_1, ..., P_j_m` that follow `P_i` in the match expression and that do overlap with `P_i`. That is, `P_i, P_j_1, ..., P_j_m` all match at least one common value.
161+
162+
2. For each such sequence, verify that `T_i <: R` under the GADT constraint arising from matching the scrutinee `t` against all of the patterns `P_i, P_j_1, ..., P_j_m`.
163+
164+
In the example above, `A` and `B` would be overlapping because they have the common subclass `C`. Hence, we have to check that the right-hand side `1` is a subtype of `M[X]`
165+
under the assumptions that `X <: A` and `X <: B`. Under these assumptions `M[X]` reduces
166+
to `1 & 2`, which gives a type error.
167+
168+
For simplicity, we have disregarded the `null` value in this discussion. `null` does not cause a fundamental problem but complicates things somewhat because some forms of patterns do not match `null`.
169+
170+
## Overlapping Patterns
171+
172+
A complete defininition of when two patterns or types overlap still needs to be worked out. Some examples we want to cover here are:
173+
174+
- Two classes overlap only if one is a subtype of the other
175+
- A final class `C` overlaps with a trait `T` only if `C` extends `T` directly or indirectly.
176+
- A class overlaps with a sealed trait `T` only if it overlaps with one of the known subclasses of `T`.
177+
- An abstract type or type parameter `A` overlaps with a type `B` only if `A`'s upper bound overlaps with `B`.
178+
- A union type `A_1 | A_2` overlaps with `B` only if `A_1` overlaps with `B` or `A_2` overlaps with `B`.
179+
- An intersection type `A_1 & A_2` overlaps with `B` only if both `A_1` and `A_2` overlap with `B`.
180+
- If C[X_1, ..., X_n] is a case class, then the instance type C[A_1, ..., A_n]` overlaps with the instance type `C[B_1, ..., B_n]` only if for every index `i` in `1..n`,
181+
if `X_i` is the type of a parameter of the class, then `A_i` overlaps with `B_i`.
182+
183+
The last rule in particular is needed to detect non-overlaps for cases where the scrutinee and the patterns are tuples. I.e. `(Int, String)` does not overlap `(Int, Int)` since
184+
`String` does not overlap `Int`.
185+
186+
187+

docs/sidebar.yml

+2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ sidebar:
2525
url: docs/reference/union-types.html
2626
- title: Type lambdas
2727
url: docs/reference/type-lambdas.html
28+
- title: Match types
29+
url: docs/reference/match-types.html
2830
- title: Implicit Function Types
2931
url: docs/reference/implicit-function-types.html
3032
- title: Dependent Function Types

0 commit comments

Comments
 (0)