Upgrade to Pro — share decks privately, control downloads, hide ads and more …

A Generic Algorithm for Checking Exhaustivity of Pattern Matching

liu fengyun
November 02, 2016

A Generic Algorithm for Checking Exhaustivity of Pattern Matching

Algebraic data types and pattern matching are key features of functional programming languages. Exhaustivity checking of pattern matching is a safety belt that defends against unmatched exceptions at runtime and boosts type safety. However, the presence of language features like inheritance, typecase, traits, GADTs, path-dependent types and union types makes the checking difficult and the algorithm complex.

In this paper we propose a generic algorithm that decouples the checking algorithm from specific type theories. The decoupling makes the algorithm simple and enables easy customization for specific type systems.

liu fengyun

November 02, 2016
Tweet

Other Decks in Science

Transcript

  1. Check Exhaustivity of Pattern Match: A Generic Algorithm Fengyun Liu

    Scala Symposium 2016, Amsterdam, Netherlands EPFL, Switzerland
  2. Pattern Matching A slip in code: val d: Option[List[Int]] =

    ... d match { case Some(x::xs) => true case None => false } 2
  3. Pattern Matching A slip in code: val d: Option[List[Int]] =

    ... d match { case Some(x::xs) => true case None => false } Warning: match may not be exhaustive. It would fail on the following input: Some(Nil) 2
  4. The Problem It’s a solved problem for ADTs L. Maranget,

    Warnings for pattern matching, Journal of Functional Programming, 2007 3
  5. The Problem It’s a solved problem for ADTs L. Maranget,

    Warnings for pattern matching, Journal of Functional Programming, 2007 How to handle growing complexity of type systems? • GADTs • subtyping • mixins • typecase • path-dependent types • union types • ... 3
  6. The Problem It’s a solved problem for ADTs L. Maranget,

    Warnings for pattern matching, Journal of Functional Programming, 2007 How to handle growing complexity of type systems? • GADTs • subtyping • mixins • typecase • path-dependent types • union types • ... A new algorithm based on space algebra • bring complexity under control 3
  7. Computability: Undecidable The compiler doesn’t know that a2 – 2ab

    + b2 + 1 = (a – b)2 + 1 > 0. (x, y) match { case (a, b) if a*a - 2*a*b + b*b + 1 > 0 => true } 4
  8. Computability: Undecidable The compiler doesn’t know that a2 – 2ab

    + b2 + 1 = (a – b)2 + 1 > 0. (x, y) match { case (a, b) if a*a - 2*a*b + b*b + 1 > 0 => true } Generally, exhaustivity check for pattern match with guards is undecidable. 4
  9. Complexity Is following code exhaustive? sealed trait O; object A

    extends O; object B extends O tuple match { case (A, A, A, A, A, _, _, _, _, _, _, _, _, _, _) => 1 case (B, _, _, _, _, A, A, A, A, _, _, _, _, _, _) => 2 case (_, B, _, _, _, B, _, _, _, A, A, A, _, _, _) => 3 case (_, _, B, _, _, _, B, _, _, B, _, _, A, A, _) => 4 case (_, _, _, B, _, _, _, B, _, _, B, _, B, _, A) => 5 case (_, _, _, _, B, _, _, _, B, _, _, B, _, B, B) => 6 } 5
  10. Complexity Is following code exhaustive? sealed trait O; object A

    extends O; object B extends O tuple match { case (A, A, A, A, A, _, _, _, _, _, _, _, _, _, _) => 1 case (B, _, _, _, _, A, A, A, A, _, _, _, _, _, _) => 2 case (_, B, _, _, _, B, _, _, _, A, A, A, _, _, _) => 3 case (_, _, B, _, _, _, B, _, _, B, _, _, A, A, _) => 4 case (_, _, _, B, _, _, _, B, _, _, B, _, B, _, A) => 5 case (_, _, _, _, B, _, _, _, B, _, _, B, _, B, B) => 6 } (B, A, B, A, A, B, B, B, A, B, A, A, B, B, B) and more 5
  11. Complexity: NP-Complete Idea: transform SAT of CNFs to exhaustivity check

    problems. For example, P = (p1 ∨ ¬p3 ) ∧ (p2 ∨ p3 ) can be transformed to following code C: sealed trait O object T extends O object F extends O (x, y, z) match { case (T, _, F) => case (_, T, T) => } 6
  12. Complexity: NP-Complete Idea: transform SAT of CNFs to exhaustivity check

    problems. For example, P = (p1 ∨ ¬p3 ) ∧ (p2 ∨ p3 ) can be transformed to following code C: sealed trait O object T extends O object F extends O (x, y, z) match { case (T, _, F) => case (_, T, T) => } C unexhaustive ⇐⇒ (x = F ∨ z = T) ∧ (y = F ∨ z = F) satisfiable 6
  13. Complexity: NP-Complete Idea: transform SAT of CNFs to exhaustivity check

    problems. For example, P = (p1 ∨ ¬p3 ) ∧ (p2 ∨ p3 ) can be transformed to following code C: sealed trait O object T extends O object F extends O (x, y, z) match { case (T, _, F) => case (_, T, T) => } C unexhaustive ⇐⇒ (x = F ∨ z = T) ∧ (y = F ∨ z = F) satisfiable ⇐⇒ P satisfiable 6
  14. The Idea • Types and patterns can be thought as

    spaces of values • types: the values that inhabit the type • patterns: the values that can be matched by the pattern 7
  15. The Idea • Types and patterns can be thought as

    spaces of values • types: the values that inhabit the type • patterns: the values that can be matched by the pattern • Some spaces can be decomposed into smaller spaces • Boolean, Int | Boolean, Option[Int] 7
  16. The Idea • Types and patterns can be thought as

    spaces of values • types: the values that inhabit the type • patterns: the values that can be matched by the pattern • Some spaces can be decomposed into smaller spaces • Boolean, Int | Boolean, Option[Int] • There can be operations on spaces • intersection, subtraction, etc 7
  17. Spaces Definition: 1. O is an empty space. 2. T

    (T) is a type space of type T. 8
  18. Spaces Definition: 1. O is an empty space. 2. T

    (T) is a type space of type T. 3. If s1, s2, · · · are spaces, then s1 | s2 | ... is a union space. 8
  19. Spaces Definition: 1. O is an empty space. 2. T

    (T) is a type space of type T. 3. If s1, s2, · · · are spaces, then s1 | s2 | ... is a union space. 4. If s1, s2, · · · , sn are spaces and K is a constructor type, then K (K, s1, s2, ..., sn) is a constructor space. 8
  20. Spaces Definition: 1. O is an empty space. 2. T

    (T) is a type space of type T. 3. If s1, s2, · · · are spaces, then s1 | s2 | ... is a union space. 4. If s1, s2, · · · , sn are spaces and K is a constructor type, then K (K, s1, s2, ..., sn) is a constructor space. 8
  21. Spaces Definition: 1. O is an empty space. 2. T

    (T) is a type space of type T. 3. If s1, s2, · · · are spaces, then s1 | s2 | ... is a union space. 4. If s1, s2, · · · , sn are spaces and K is a constructor type, then K (K, s1, s2, ..., sn) is a constructor space. For example: • T (Int) is the space for Int • K (Some, T (Int)) is the space for Some(_: Int) • T (Int) | T (Boolean) is the space for Int | Boolean 8
  22. Reformulation of Exhaustivity Check Problem The problem of exhaustivity check

    in terms of spaces: Is the space T (T), where T is the type of the value to be matched against, a subspace of the union of spaces covered by all the pattern clauses? 9
  23. Reformulation of Exhaustivity Check Problem The problem of exhaustivity check

    in terms of spaces: Is the space T (T), where T is the type of the value to be matched against, a subspace of the union of spaces covered by all the pattern clauses? Example: val o: Option[Int] = ... o match { case Some(x) => x case None => 0 } 9
  24. Reformulation of Exhaustivity Check Problem The problem of exhaustivity check

    in terms of spaces: Is the space T (T), where T is the type of the value to be matched against, a subspace of the union of spaces covered by all the pattern clauses? Example: val o: Option[Int] = ... o match { case Some(x) => x case None => 0 } Is T (Option[Int]) a subspace of K (Some, T (Int)) | T (None.type)? 9
  25. The Decoupling The Generic Part (abstract space algebra): • s1

    ⪯ s2: whether s1 is subspace of s2 • s . = O: whether s1 is equal to the empty space • s1 ⊓ s2: intersection of two spaces • s1 ⊖ s2: subtraction of two spaces The Concrete Part (APIs to the type system): • T1 <: T2: whether T1 is a subtype of T2 • sig(K): get parameter types of the constructor type K • D?(T): whether the type T is decomposable • D(T): decompose the type T into a union of subspaces • P(p): projects a pattern p to a space 10
  26. Definition of Subspace (⪯) Definition (Subspace) s1 ⪯ s2 if

    and only if s1 ⊖ s2 . = O Notations: • s . = O: equality with the empty space • s1 ⊖ s2: subtraction of two spaces 11
  27. Definition of Equality (s . = O) No need for

    a general theory of space equality. O . = O 12
  28. Definition of Equality (s . = O) No need for

    a general theory of space equality. O . = O T (T) . = O if D?(T) ∧ D(T) . = O 12
  29. Definition of Equality (s . = O) No need for

    a general theory of space equality. O . = O T (T) . = O if D?(T) ∧ D(T) . = O s1 | s2 | · · · . = O if ∀i.si . = O 12
  30. Definition of Equality (s . = O) No need for

    a general theory of space equality. O . = O T (T) . = O if D?(T) ∧ D(T) . = O s1 | s2 | · · · . = O if ∀i.si . = O K (K, s1, s2, · · · ) . = O if ∃i.si . = O 12
  31. Definition of Intersection (s1 ⊓ s2) 1 O ⊓ x

    = O 2 x ⊓ O = O 3 T (T1) ⊓ T (T2) = T (T1) if T1 <: T2 4 T (T1) ⊓ T (T2) = T (T2) if T2 <: T1 5 T (T) ⊓ K (K, s1, · · · ) = K (K, s1, · · · ) if K <: T 6 K (K, s1, · · · ) ⊓ T (T) = K (K, s1, · · · ) if K <: T 7 (s1 | s2 | · · · ) ⊓ x = s1 ⊓ x | s2 ⊓ x | · · · 8 x ⊓ (s1 | s2 | · · · ) = x ⊓ s1 | x ⊓ s2 | · · · 9 K (K, s1, · · · ) ⊓ K (K, w1, · · · ) = K (K, s1 ⊓ w1, s2 ⊓ w2, · · · ) 10 T (T) ⊓ x = D(T) ⊓ x if D?(T) 11 x ⊓ T (T) = x ⊓ D(T) if D?(T) 12 a ⊓ b = O otherwise 13
  32. Definition of Intersection (s1 ⊓ s2) 1 O ⊓ x

    = O 2 x ⊓ O = O 3 T (T1) ⊓ T (T2) = T (T1) if T1 <: T2 4 T (T1) ⊓ T (T2) = T (T2) if T2 <: T1 5 T (T) ⊓ K (K, s1, · · · ) = K (K, s1, · · · ) if K <: T 6 K (K, s1, · · · ) ⊓ T (T) = K (K, s1, · · · ) if K <: T 7 (s1 | s2 | · · · ) ⊓ x = s1 ⊓ x | s2 ⊓ x | · · · 8 x ⊓ (s1 | s2 | · · · ) = x ⊓ s1 | x ⊓ s2 | · · · 9 K (K, s1, · · · ) ⊓ K (K, w1, · · · ) = K (K, s1 ⊓ w1, s2 ⊓ w2, · · · ) 10 T (T) ⊓ x = D(T) ⊓ x if D?(T) 11 x ⊓ T (T) = x ⊓ D(T) if D?(T) 12 a ⊓ b = O otherwise 13
  33. Intersection of two constructor spaces K (K, s1, · ·

    · ) ⊓ K (K, w1, · · · ) = K (K, s1 ⊓ w1, s2 ⊓ w2, · · · ) (None, Some) ⊓ (Some, Some) 14
  34. Intersection of two constructor spaces K (K, s1, · ·

    · ) ⊓ K (K, w1, · · · ) = K (K, s1 ⊓ w1, s2 ⊓ w2, · · · ) (None, Some) ⊓ (Some, Some) = (None ⊓ Some, Some ⊓ Some) 14
  35. Intersection of two constructor spaces K (K, s1, · ·

    · ) ⊓ K (K, w1, · · · ) = K (K, s1 ⊓ w1, s2 ⊓ w2, · · · ) (None, Some) ⊓ (Some, Some) = (None ⊓ Some, Some ⊓ Some) = (O, Some) 14
  36. Intersection of two constructor spaces K (K, s1, · ·

    · ) ⊓ K (K, w1, · · · ) = K (K, s1 ⊓ w1, s2 ⊓ w2, · · · ) (None, Some) ⊓ (Some, Some) = (None ⊓ Some, Some ⊓ Some) = (O, Some) = O 14
  37. Intersection of two constructor spaces K (K, s1, · ·

    · ) ⊓ K (K, w1, · · · ) = K (K, s1 ⊓ w1, s2 ⊓ w2, · · · ) (None, Some) ⊓ (Some, Some) = (None ⊓ Some, Some ⊓ Some) = (O, Some) = O (None, Some) ⊓ (_, _) 14
  38. Intersection of two constructor spaces K (K, s1, · ·

    · ) ⊓ K (K, w1, · · · ) = K (K, s1 ⊓ w1, s2 ⊓ w2, · · · ) (None, Some) ⊓ (Some, Some) = (None ⊓ Some, Some ⊓ Some) = (O, Some) = O (None, Some) ⊓ (_, _) = (None ⊓ _, Some ⊓ _) 14
  39. Intersection of two constructor spaces K (K, s1, · ·

    · ) ⊓ K (K, w1, · · · ) = K (K, s1 ⊓ w1, s2 ⊓ w2, · · · ) (None, Some) ⊓ (Some, Some) = (None ⊓ Some, Some ⊓ Some) = (O, Some) = O (None, Some) ⊓ (_, _) = (None ⊓ _, Some ⊓ _) = (None, Some) 14
  40. Definition of Subtraction (s1 ⊖ s2) 1 O ⊖ x

    = O 2 x ⊖ O = x 3 T (T1) ⊖ T (T2) = O if T1 <: T2 4 T (K) ⊖ K (K, s1, · · · ) = K (K, map T sig(K)) ⊖ K (K, s1, · · · ) 5 (s1 | s2 | · · · ) ⊖ x = s1 ⊖ x | s2 ⊖ x | · · · 6 x ⊖ (s1 | s2 | · · · ) = x ⊖ s1 ⊖ s2 ⊖ · · · 7 K (K, s1, · · · ) ⊖ T (T) = O if K <: T 8 K (K, s1, · · · ) ⊖ K (K, w1, · · · ) = O if ∀i.si ⪯ wi 9 K (K, s1, · · · ) ⊖ K (K, w1, · · · ) = K (K, s1, · · · ) if ∃i.si ⊓ wi . = O 10 K (K, s1, · · · ) ⊖ K (K, w1, · · · ) = K (K, s1 ⊖ w1, s2, · · · ) | K (K, s1, s2 ⊖ w2, · · · ) | · · · 11 T (T) ⊖ x = D(T) ⊖ x if D?(T) 12 x ⊖ T (T) = x ⊖ D(T) if D?(T) 13 a ⊖ b = a otherwise 15
  41. Definition of Subtraction (s1 ⊖ s2) 1 O ⊖ x

    = O 2 x ⊖ O = x 3 T (T1) ⊖ T (T2) = O if T1 <: T2 4 T (K) ⊖ K (K, s1, · · · ) = K (K, map T sig(K)) ⊖ K (K, s1, · · · ) 5 (s1 | s2 | · · · ) ⊖ x = s1 ⊖ x | s2 ⊖ x | · · · 6 x ⊖ (s1 | s2 | · · · ) = x ⊖ s1 ⊖ s2 ⊖ · · · 7 K (K, s1, · · · ) ⊖ T (T) = O if K <: T 8 K (K, s1, · · · ) ⊖ K (K, w1, · · · ) = O if ∀i.si ⪯ wi 9 K (K, s1, · · · ) ⊖ K (K, w1, · · · ) = K (K, s1, · · · ) if ∃i.si ⊓ wi . = O 10 K (K, s1, · · · ) ⊖ K (K, w1, · · · ) = K (K, s1 ⊖ w1, s2, · · · ) | K (K, s1, s2 ⊖ w2, · · · ) | · · · 11 T (T) ⊖ x = D(T) ⊖ x if D?(T) 12 x ⊖ T (T) = x ⊖ D(T) if D?(T) 13 a ⊖ b = a otherwise 15
  42. Subtraction of two constructor spaces K (K, s1, · ·

    · ) ⊖ K (K, w1, · · · ) = K (K, s1 ⊖ w1, s2, · · · ) | K (K, s1, s2 ⊖ w2, · · · ) | · · · val x, y: Option[Int] = ... (x, y) match { case (None, Some(_)) => true } 16
  43. Subtraction of two constructor spaces K (K, s1, · ·

    · ) ⊖ K (K, w1, · · · ) = K (K, s1 ⊖ w1, s2, · · · ) | K (K, s1, s2 ⊖ w2, · · · ) | · · · val x, y: Option[Int] = ... (x, y) match { case (None, Some(_)) => true } (_, _) ⊖ (None, Some(_)) = 16
  44. Subtraction of two constructor spaces K (K, s1, · ·

    · ) ⊖ K (K, w1, · · · ) = K (K, s1 ⊖ w1, s2, · · · ) | K (K, s1, s2 ⊖ w2, · · · ) | · · · val x, y: Option[Int] = ... (x, y) match { case (None, Some(_)) => true } (_, _) ⊖ (None, Some(_)) = (_ ⊖ None, _) | (_, _ ⊖ Some(_)) 16
  45. Subtraction of two constructor spaces K (K, s1, · ·

    · ) ⊖ K (K, w1, · · · ) = K (K, s1 ⊖ w1, s2, · · · ) | K (K, s1, s2 ⊖ w2, · · · ) | · · · val x, y: Option[Int] = ... (x, y) match { case (None, Some(_)) => true } (_, _) ⊖ (None, Some(_)) = (_ ⊖ None, _) | (_, _ ⊖ Some(_)) = (Some(_), _) | (_, None) 16
  46. Subtraction of two constructor spaces K (K, s1, · ·

    · ) ⊖ K (K, w1, · · · ) = K (K, s1 ⊖ w1, s2, · · · ) | K (K, s1, s2 ⊖ w2, · · · ) | · · · val x, y: Option[Int] = ... (x, y) match { case (None, Some(_)) => true } (_, _) ⊖ (None, Some(_)) = (_ ⊖ None, _) | (_, _ ⊖ Some(_)) = (Some(_), _) | (_, None) Naive formulation (_, _) ⊖ (None, Some(_)) = (_ ⊖ None, _ ⊖ Some(_)) 16
  47. Subtraction of two constructor spaces K (K, s1, · ·

    · ) ⊖ K (K, w1, · · · ) = K (K, s1 ⊖ w1, s2, · · · ) | K (K, s1, s2 ⊖ w2, · · · ) | · · · val x, y: Option[Int] = ... (x, y) match { case (None, Some(_)) => true } (_, _) ⊖ (None, Some(_)) = (_ ⊖ None, _) | (_, _ ⊖ Some(_)) = (Some(_), _) | (_, None) Naive formulation (_, _) ⊖ (None, Some(_)) = (_ ⊖ None, _ ⊖ Some(_)) = (Some(_), None) 16
  48. An Assumption of the Algorithm Assumption A constructor type K

    cannot be a super type of other types. 17
  49. An Assumption of the Algorithm Assumption A constructor type K

    cannot be a super type of other types. Otherwise, it’s unknown how to define the following: T (T) ⊖ K (K, s1, · · · ) = ? if T <: K K (K, s1, · · · ) ⊖ T (T) = ? if T <: K 17
  50. An Assumption of the Algorithm Assumption A constructor type K

    cannot be a super type of other types. Otherwise, it’s unknown how to define the following: T (T) ⊖ K (K, s1, · · · ) = ? if T <: K K (K, s1, · · · ) ⊖ T (T) = ? if T <: K T (T) ⊓ K (K, s1, · · · ) = ? if T <: K K (K, s1, · · · ) ⊓ T (T) = ? if T <: K 17
  51. An Assumption of the Algorithm Assumption A constructor type K

    cannot be a super type of other types. Otherwise, it’s unknown how to define the following: T (T) ⊖ K (K, s1, · · · ) = ? if T <: K K (K, s1, · · · ) ⊖ T (T) = ? if T <: K T (T) ⊓ K (K, s1, · · · ) = ? if T <: K K (K, s1, · · · ) ⊓ T (T) = ? if T <: K Note It’s possible to extend a case class in Scala, but it’s an anti-pattern! 17
  52. Intricacy in the formalization Simple, but not trivial: • Union

    (|) is a type, while • Intersection (⊓) and subtraction (⊖) are functions Why? 18
  53. Correctness • Implementation for Dotty 1 (400LOC VS. 1400LOC) •

    Pass regression test • Fix 13 open issues • No formal proof yet 1https://github.com/lampepfl/dotty/pull/1364 19
  54. Performance Series I, S, V and T from Maranget’s paper(2007).

    100 200 300 400 500 0 0.5 1 1.5 2 ·108 Series I dotty scala 4 6 8 10 107 108 109 1010 1011 1012 1013 Series S dotty scala 1 2 3 4 5 6 106 108 1010 1012 Series V dotty scala 0 5 10 15 20 106 107 108 109 1010 Series T dotty scala Figure 1: Performance comparison (time unit: ns) 20
  55. Limitation The algorithm is ignorant of type constraints between constructor

    parameters. sealed trait Expr[T] case class IExpr(x: Int) extends Expr[Int] case class BExpr(b: Boolean) extends Expr[Boolean] def foo[T](x: Expr[T], y: Expr[T]) = (x, y) match { case (IExpr(_), IExpr(_)) => true case (BExpr(_), BExpr(_)) => false } 21
  56. Limitation The algorithm is ignorant of type constraints between constructor

    parameters. sealed trait Expr[T] case class IExpr(x: Int) extends Expr[Int] case class BExpr(b: Boolean) extends Expr[Boolean] def foo[T](x: Expr[T], y: Expr[T]) = (x, y) match { case (IExpr(_), IExpr(_)) => true case (BExpr(_), BExpr(_)) => false } warning: match may not be exhaustive. It would fail on the following input: (BExpr(_), IExpr(_)), (IExpr(_), BExpr(_)) 21
  57. Limitation The algorithm is ignorant of type constraints between constructor

    parameters. sealed trait Expr[T] case class IExpr(x: Int) extends Expr[Int] case class BExpr(b: Boolean) extends Expr[Boolean] def foo[T](x: Expr[T], y: Expr[T]) = (x, y) match { case (IExpr(_), IExpr(_)) => true case (BExpr(_), BExpr(_)) => false } warning: match may not be exhaustive. It would fail on the following input: (BExpr(_), IExpr(_)), (IExpr(_), BExpr(_)) Possible fix: ask typer to type check counterexamples in the case of GADTs. 21
  58. Conclusion • Space algebra: intersection (⊓), subtraction (⊖), emptiness (s

    . = O) • Decoupling of generic algorithm from concrete type system • Simple, intuitive and easy to generate counterexamples • Works well for practical programs 22
  59. Intricacy in the formalization Simple, but not trivial: • Union

    (|) is a type, while • Intersection (⊓) and subtraction (⊖) are functions Why? Union must be a type: • (Some, None) | (None, Some) 23
  60. From Patterns to Spaces (P) The definition of the function

    P is the responsibility of concrete algorithms. Generally, following rules should be followed: P(x : T) = T (T) P(p1 | p2 | · · · ) = P(p1 ) | P(p2 ) | · · · P(K(p1 , p2 , · · · )) = K (K, P(p1 ), P(p2 ), · · · ) 24
  61. Series I abstract sealed trait C case object C1 extends

    C case object C2 extends C case object C3 extends C case object C4 extends C case object C5 extends C c match { case C1 => 1 case C2 => 2 case C3 => 3 case C4 => 4 case C5 => 5 } 25
  62. Series S sealed trait O object A extends O object

    B extends O tuple match { case (A, A, _, _, _, _, _, _) => 1 case (_, _, A, A, _, _, _, _) => 2 case (_, _, _, _, A, A, _, _) => 3 case (_, _, _, _, _, _, A, A) => 4 case (B, A, B, A, B, A, B, A) => 5 } 26
  63. Series V sealed trait O object A extends O object

    B extends O tuple match { case (A, A, A, A, A, _, _, _, _, _, _, _, _, _, _) => 1 case (B, _, _, _, _, A, A, A, A, _, _, _, _, _, _) => 2 case (_, B, _, _, _, B, _, _, _, A, A, A, _, _, _) => 3 case (_, _, B, _, _, _, B, _, _, B, _, _, A, A, _) => 4 case (_, _, _, B, _, _, _, B, _, _, B, _, B, _, A) => 5 case (_, _, _, _, B, _, _, _, B, _, _, B, _, B, B) => 6 } 27
  64. Series T sealed trait O object A extends O object

    B extends O tuple match { case (A, A, A, A, A) => 1 case (B, B, B, B, B) => 2 case (_, A, A, A, A) => 3 case (_, B, B, B, B) => 4 case (_, _, A, A, A) => 5 case (_, _, B, B, B) => 6 case (_, _, _, A, A) => 7 case (_, _, _, B, B) => 8 case (_, _, _, _, A) => 9 case (_, _, _, _, B) => 10 } 28
  65. Why define subspace indirectly The difficulty is as follows: •

    Is (_, _) as subspace of (Some, None) | (None, Some) Subtraction seems unavoidable in such cases. 22