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 )
75import 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' )
2025import 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-}
2633subV :: Text -> Pat -> Pat -> Pat
2734subV varNm t = \ case
@@ -64,39 +71,28 @@ getSubs (RefP n t) (RefP n' t') = getSubs n n' <> getSubs t t'
6471getSubs (DecP a b c) (DecP a' b' c') = getSubs a a' <> getSubs b b' <> getSubs c c'
6572getSubs _ _ = []
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