Skip to content

Commit 59242c5

Browse files
committed
Added a TyVarP pat to distinguish pattern/ type variables. Modified solve to operate in a ReaderT/WriterT/ExcepT monadic context. Removed the mostSpecificInstance machinery. Added an error for overlaps
1 parent 9ea8c18 commit 59242c5

File tree

3 files changed

+60
-69
lines changed

3 files changed

+60
-69
lines changed

lambda-buffers-compiler/src/LambdaBuffers/Compiler/TypeClass/Pat.hs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,14 @@ cost of significantly more complex type signatures.
1919
-}
2020

2121
data Pat
22-
= {- extremely stupid, unfortunately necessary -}
22+
= {- Name / ModuleName / Opaque / TyVarP are literal patterns (or ground terms)
23+
because hey cannot contain any VarPs and therefore "have no holes".
24+
Every TyDef or subcomponent thereof will be translated into a composite
25+
pattern "without any holes". (Nil is also a literal/ground term, I guess) -}
2326
Name Text
24-
| ModuleName [Text] -- also stupid, also necessary -_-
27+
| ModuleName [Text]
2528
| Opaque
29+
| TyVarP Text
2630
| {- Lists (constructed from Nil and :*) with bare types are used to
2731
encode products (where a list of length n encodes an n-tuple)
2832
Lists with field labels (l := t) are used to encode records and sum types
@@ -45,10 +49,8 @@ data Pat
4549
| ProdP Pat {- Pat arg should be a list of "Bare types" -}
4650
| SumP Pat {- where the Pat arg is expected to be (Constr l t :* rest) or Nil, where
4751
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+
| VarP Text {- This isn't a type variable. It is used more generally to refer to any "hole" in a pattern into
53+
to which another pattern may be substituted. TyVarP is the literal pattern / ground term for TyVars -}
5254
| RefP Pat Pat {- 1st arg should be a ModuleName -}
5355
| AppP Pat Pat {- Pattern for Type applications -}
5456
| {- This last one is a bit special. This represents a complete type declaration.

lambda-buffers-compiler/src/LambdaBuffers/Compiler/TypeClass/Pretty.hs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ module LambdaBuffers.Compiler.TypeClass.Pretty (
1010
) where
1111

1212
import Data.Generics.Labels ()
13+
import Data.Text qualified as T
1314
import LambdaBuffers.Compiler.ProtoCompat qualified as P
14-
import LambdaBuffers.Compiler.TypeClass.Pat (Pat (AppP, DecP, ModuleName, Name, Nil, Opaque, ProdP, RecP, RefP, SumP, VarP, (:*), (:=)), patList)
15+
import LambdaBuffers.Compiler.TypeClass.Pat (Pat (AppP, DecP, ModuleName, Name, Nil, Opaque, ProdP, RecP, RefP, SumP, TyVarP, VarP, (:*), (:=)), patList)
1516
import LambdaBuffers.Compiler.TypeClass.Rules (
1617
Class (Class),
1718
Constraint (C),
@@ -58,6 +59,7 @@ instance Pretty Pat where
5859
Name t -> pretty t
5960
ModuleName ts -> hcat . punctuate "." . map pretty $ ts
6061
Opaque -> "<OPAQUE>"
62+
TyVarP t -> pretty t
6163
RecP ps -> case patList ps of
6264
Nothing -> pretty ps
6365
Just fields -> case traverse prettyField fields of
@@ -80,7 +82,8 @@ instance Pretty Pat where
8082
RefP mn@(ModuleName _) n@(Name _) -> pretty mn <> "." <> pretty n
8183
RefP Nil (Name n) -> pretty n
8284
RefP p1 p2 -> parens $ "Ref" <+> pretty p1 <+> pretty p2
83-
VarP t -> pretty t
85+
-- Pattern variables are uppercased to distinguish them from proper TyVars
86+
VarP t -> pretty (T.toUpper t)
8487
ap@(AppP p1 p2) -> case prettyApp ap of
8588
Just pap -> parens pap
8689
Nothing -> "App" <+> pretty p1 <+> pretty p2
Lines changed: 47 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
{-# LANGUAGE LambdaCase #-}
22

3-
module LambdaBuffers.Compiler.TypeClass.Solve (solve) where
3+
module LambdaBuffers.Compiler.TypeClass.Solve (solveM, solve, Overlap (..)) where
44

5-
import Data.List (foldl', sortBy)
6-
import Data.Text (Text)
75
import LambdaBuffers.Compiler.TypeClass.Pat (
86
Pat (AppP, DecP, ProdP, RecP, RefP, SumP, VarP, (:*), (:=)),
97
matches,
@@ -17,11 +15,20 @@ import LambdaBuffers.Compiler.TypeClass.Rules (
1715
ruleHeadPat,
1816
)
1917

18+
import Control.Monad.Except (throwError)
19+
import Control.Monad.Reader (ReaderT, runReaderT)
20+
import Control.Monad.Reader.Class (MonadReader (ask))
21+
import Control.Monad.Writer.Class (MonadWriter (tell))
22+
import Control.Monad.Writer.Strict (WriterT, execWriterT)
23+
import Data.Foldable (traverse_)
24+
import Data.List (foldl')
2025
import Data.Set qualified as S
26+
import Data.Text (Text)
2127

22-
{- Variable substitution. Given a string that represents a variable name,
23-
and a type to instantiate variables with that name to, performs the
24-
instantiation
28+
{- Pattern/Template/Unification variable substitution.
29+
Given a string that represents a variable name,
30+
and a type to instantiate variables with that name to,
31+
performs the instantiation
2532
-}
2633
subV :: Text -> Pat -> Pat -> Pat
2734
subV varNm t = \case
@@ -64,39 +71,28 @@ getSubs (RefP n t) (RefP n' t') = getSubs n n' <> getSubs t t'
6471
getSubs (DecP a b c) (DecP a' b' c') = getSubs a a' <> getSubs b b' <> getSubs c c'
6572
getSubs _ _ = []
6673

67-
-- should be vastly more efficient than Data.List.Nub
68-
deduplicate :: Ord a => [a] -> [a]
69-
deduplicate = S.toList . S.fromList
74+
-- NoMatch isn't fatal but OverlappingMatches is (i.e. we need to stop when we encounter it)
75+
data MatchError
76+
= NoMatch
77+
| OverlappingMatches [Rule]
7078

71-
-- is the first pattern a substitution instance of the second
72-
isSubstitutionOf :: Pat -> Pat -> Bool
73-
isSubstitutionOf p1 p2 = matches p2 p1
79+
-- for SolveM, since we catch NoMatch
80+
data Overlap = Overlap Constraint [Rule]
81+
deriving stock (Show, Eq)
7482

75-
compareSpecificity :: Pat -> Pat -> Ordering
76-
compareSpecificity p1 p2
77-
| p1
78-
`isSubstitutionOf` p2
79-
&& p2
80-
`isSubstitutionOf` p1 =
81-
EQ
82-
| p1 `isSubstitutionOf` p2 = LT
83-
| otherwise = GT
84-
85-
sortOnSpecificity :: Pat -> Class -> [Rule] -> [Rule]
86-
sortOnSpecificity p c ps =
87-
sortBy (\a1 a2 -> compareSpecificity (ruleHeadPat a1) (ruleHeadPat a2)) $
88-
filter matchPatAndClass ps
83+
selectMatchingInstance :: Pat -> Class -> [Rule] -> Either MatchError Rule
84+
selectMatchingInstance p c rs = case filter matchPatAndClass rs of
85+
[] -> Left NoMatch
86+
[r] -> Right r
87+
overlaps -> Left $ OverlappingMatches overlaps
8988
where
9089
matchPatAndClass :: Rule -> Bool
9190
matchPatAndClass r =
9291
ruleHeadClass r == c
9392
&& ruleHeadPat r
9493
`matches` p
9594

96-
mostSpecificInstance :: Pat -> Class -> [Rule] -> Maybe Rule
97-
mostSpecificInstance p c ps = case sortOnSpecificity p c ps of
98-
[] -> Nothing
99-
(x : _) -> Just x
95+
type SolveM = ReaderT [Rule] (WriterT (S.Set Constraint) (Either Overlap))
10096

10197
{- Given a list of instances (the initial scope), determines whether we can derive
10298
an instance of the Class argument for the Pat argument. A result of [] indicates that there are
@@ -108,38 +104,28 @@ mostSpecificInstance p c ps = case sortOnSpecificity p c ps of
108104
it doesn't b/c it's *impossible* to have `instance Foo X` if the definition of Foo is
109105
`class Bar y => Foo y` without an `instance Bar X`)
110106
-}
111-
solve ::
112-
[Rule] -> -- all instance rules in scope. WE ASSUME THESE HAVE ALREADY BEEN GENERATED, SOMEWHERE
113-
Constraint -> -- constraint we're trying to solve
114-
[Constraint] -- subgoals that cannot be solved for w/ the current rule set
115-
solve inScope cst@(C c pat) =
116-
-- First, we look for the most specific instance...
117-
case mostSpecificInstance pat c inScope of
118-
-- If there isn't one, we return only the constraint we were trying to solve
119-
Nothing -> [cst]
120-
-- If there is, we substitute the argument of the constraint to be solved into the matching rules
121-
Just rule -> case subst rule pat of
122-
-- If there are no additional constraints on the rule, we try to solve the superclasses
123-
C _ p :<= [] -> case csupers c of
124-
[] -> []
125-
xs -> solveClassesFor p xs
126-
-- If there are additional constraints on the rule, we try to solve them
127-
C _ _ :<= is -> case concatMap (solve inScope) is of
128-
-- If we succeed at solving the additional constraints on the rule, we try to solve the supers
129-
[] -> solveClassesFor pat (csupers c)
130-
-- We deduplicate the list of unsolvable subgoals
131-
xs -> deduplicate xs
107+
solveM :: Constraint -> SolveM ()
108+
solveM cst@(C c pat) =
109+
ask >>= \inScope ->
110+
-- First, we look for the most specific instance...
111+
case selectMatchingInstance pat c inScope of
112+
Left e -> case e of
113+
NoMatch -> tell $ S.singleton cst
114+
OverlappingMatches olps -> throwError $ Overlap cst olps
115+
-- If there is, we substitute the argument of the constraint to be solved into the matching rules
116+
Right rule -> case subst rule pat of
117+
-- If there are no additional constraints on the rule, we try to solve the superclasses
118+
C _ p :<= [] -> solveClassesFor p (csupers c)
119+
-- If there are additional constraints on the rule, we try to solve them
120+
C _ _ :<= is -> do
121+
traverse_ solveM is
122+
solveClassesFor pat (csupers c)
132123
where
133124
-- NOTE(@bladyjoker): The version w/ flip is more performant...
134125
-- Given a Pat and a list of Classes, attempt to solve the constraints
135126
-- constructed from the Pat and each Class
136-
solveClassesFor :: Pat -> [Class] -> [Constraint]
137-
solveClassesFor p =
138-
deduplicate -- multiple constraints could emit the same subgoal which is bad
139-
. concatMap
140-
( solve inScope
141-
. ( \cls ->
142-
let classConstraint = C cls p
143-
in classConstraint
144-
)
145-
)
127+
solveClassesFor :: Pat -> [Class] -> SolveM ()
128+
solveClassesFor p = traverse_ (\cls -> solveM (C cls p))
129+
130+
solve :: [Rule] -> Constraint -> Either Overlap [Constraint]
131+
solve rules c = fmap S.toList $ execWriterT $ runReaderT (solveM c) rules

0 commit comments

Comments
 (0)