|
| 1 | +{-# LANGUAGE LambdaCase #-} |
| 2 | + |
| 3 | +module LambdaBuffers.Compiler.TypeClass.Pat ( |
| 4 | + Pat (..), |
| 5 | + toProd, |
| 6 | + toRec, |
| 7 | + toSum, |
| 8 | + patList, |
| 9 | + matches, |
| 10 | +) where |
| 11 | + |
| 12 | +import Data.Text (Text) |
| 13 | + |
| 14 | +{- A simple ADT to represent patterns. |
| 15 | +
|
| 16 | +Note that this ADT allows us to represent nonsensical types (i.e. we can "put the wrong pattern in a hole"). |
| 17 | +This could be ameliorated by using a GADT, which would give us correct-by-construction patterns at the |
| 18 | +cost of significantly more complex type signatures. |
| 19 | +-} |
| 20 | + |
| 21 | +data Pat |
| 22 | + = {- extremely stupid, unfortunately necessary -} |
| 23 | + Name Text |
| 24 | + | ModuleName [Text] -- also stupid, also necessary -_- |
| 25 | + | Opaque |
| 26 | + | {- Lists (constructed from Nil and :*) with bare types are used to |
| 27 | + encode products (where a list of length n encodes an n-tuple) |
| 28 | + Lists with field labels (l := t) are used to encode records and sum types |
| 29 | + These representations let us "peer into the structure" of the TyBody, and are |
| 30 | + somewhat analogous to the Generics.SOP representation or, in the case of records (or sums |
| 31 | + interpreted as variants), to a row-types representation. We can imagine that each record and |
| 32 | + sum are backed by an implicit row. |
| 33 | + Unfortunately this encoding allows us to generate Pats which do not correspond to |
| 34 | + any possible types. For the purposes of instance resolution/code generation this shouldn't matter |
| 35 | + so long as the patterns are only generalizations of "real" types. We could ameliorate this problem by |
| 36 | + using a GADT for Pat, but this would greatly complicate the constraint solving/deriving |
| 37 | + algorithms and require copious use of type families (and possibly singletons). |
| 38 | + -} |
| 39 | + Nil -- Nil and :* are hacks to write rules for ProdP and SumP. A bare Nil == Unit |
| 40 | + | Pat :* Pat -- cons |
| 41 | + | Pat := Pat {- field labels or constr names. The LHS should be (Name "Foo") |
| 42 | + for schema types, but should be a PatVar for deriving rules and instances -} |
| 43 | + | RecP Pat {- where the Pat arg is expected to be (l := t :* rest) or Nil, where rest |
| 44 | + is also a pat-list of labeled fields or Nil -} |
| 45 | + | ProdP Pat {- Pat arg should be a list of "Bare types" -} |
| 46 | + | SumP Pat {- where the Pat arg is expected to be (Constr l t :* rest) or Nil, where |
| 47 | + rest is either Nil or a tyList of Constrs -} |
| 48 | + | VarP Text {- This isn't a type variable. Although it is used to represent them in certain contexts, |
| 49 | + it is also used more generally to refer to any "hole" in a pattern to which another pattern |
| 50 | + may be substituted. We could have separate constr for type variables but it doesn't appear to be |
| 51 | + necessary at this time. -} |
| 52 | + | RefP Pat Pat {- 1st arg should be a ModuleName -} |
| 53 | + | AppP Pat Pat {- Pattern for Type applications -} |
| 54 | + | {- This last one is a bit special. This represents a complete type declaration. |
| 55 | + The first Pat should be instantiated to `Name l` where l is a concrete name. |
| 56 | + The second Pat should be instantiated to a Pat-List (using :*/Nil) which only contains Names. |
| 57 | + The final Pat should be instantiated to a Pat body. |
| 58 | + In some languages, parts of this may be ignored. E.g. in Rust the type name doesn't matter (we use the constr name of the |
| 59 | + outermost inner sum for constructing types). -} |
| 60 | + DecP Pat Pat Pat |
| 61 | + deriving stock (Show, Eq, Ord) |
| 62 | + |
| 63 | +infixr 5 :* |
| 64 | + |
| 65 | +{- Utility functions. Turn a list of types into a product/record/sum type. |
| 66 | +-} |
| 67 | +toProd :: [Pat] -> Pat |
| 68 | +toProd = ProdP . foldr (:*) Nil |
| 69 | + |
| 70 | +toRec :: [Pat] -> Pat |
| 71 | +toRec = RecP . foldr (:*) Nil |
| 72 | + |
| 73 | +toSum :: [Pat] -> Pat |
| 74 | +toSum = SumP . foldr (:*) Nil |
| 75 | + |
| 76 | +{- Converts a pattern that consists of a well formed pattern list |
| 77 | + (i.e. patterns formed from :* and Nil) into a list of patterns. |
| 78 | +-} |
| 79 | +patList :: Pat -> Maybe [Pat] |
| 80 | +patList = \case |
| 81 | + Nil -> Just [] |
| 82 | + p1 :* p2 -> (p1 :) <$> patList p2 |
| 83 | + _ -> Nothing |
| 84 | + |
| 85 | +{- This is used as a predicate to filter instances or Gens which are structurally compatible |
| 86 | + with the argument type. |
| 87 | + The first argument is the inner Pat from an instance head or Gen. |
| 88 | + The second argument is the Pat representation of a type that we want to derive an instance / generate code for. |
| 89 | + NOTE: Is not bidirectional! The first Pat has to be more general than the first |
| 90 | + (more specifically: The second Pat should be a substitution instance of the first) |
| 91 | +-} |
| 92 | +matches :: Pat -> Pat -> Bool |
| 93 | +matches t1 t2 | t1 == t2 = True -- need the guard |
| 94 | +matches (VarP _) _ = True |
| 95 | +matches (x :* xs) (x' :* xs') = matches x x' && matches xs xs' |
| 96 | +matches (l := t) (l' := t') = matches l l' && matches t t' |
| 97 | +matches (ProdP xs) (ProdP xs') = matches xs xs' |
| 98 | +matches (RecP xs) (RecP xs') = matches xs xs' |
| 99 | +matches (SumP xs) (SumP xs') = matches xs xs' |
| 100 | +matches (AppP t1 t2) (AppP t1' t2') = matches t1 t1' && matches t2 t2' |
| 101 | +matches (RefP mn t1) (RefP mn' t2) = matches mn mn' && matches t1 t2 |
| 102 | +matches (DecP t1 t2 t3) (DecP t1' t2' t3') = |
| 103 | + matches t1 t1' && matches t2 t2' && matches t3 t3' |
| 104 | +matches _ _ = False |
0 commit comments