|
| 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 | + |
0 commit comments