zwirn-0.1.0.0: src/Zwirn/Language/TypeCheck/Infer.hs
{-# LANGUAGE OverloadedStrings #-}
module Zwirn.Language.TypeCheck.Infer
( inferTerm,
generalize,
)
where
{-
Infer.hs - type inference algorithm adapted from
https://github.com/sdiehl/write-you-a-haskell/tree/master/chapter7/poly_constraints
Copyright (C) 2023, Martin Gius
This library is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this library. If not, see <http://www.gnu.org/licenses/>.
-}
import Control.Monad (replicateM)
import Control.Monad.Except
import Control.Monad.Reader
import Control.Monad.State
import Data.List (nub)
import qualified Data.Map as Map
import qualified Data.Set as Set
import Data.Text (Text, pack)
-- import Zwirn.Language.TypeCheck.Env as Env
import Zwirn.Language.Environment
import Zwirn.Language.Simple
import Zwirn.Language.TypeCheck.Constraint
import Zwirn.Language.TypeCheck.Types
-- | Inference monad
type Infer a =
( ReaderT
InterpreterEnv -- Typing environment
( StateT -- Inference state
InferState
( Except -- Inference errors
TypeError
)
)
a -- Result
)
-- | Inference state
newtype InferState = InferState {count :: Int}
-- | Initial inference state
initInfer :: InferState
initInfer = InferState {count = 0}
-------------------------------------------------------------------------------
-- Inference
-------------------------------------------------------------------------------
-- | Run the inference monad
runInfer :: InterpreterEnv -> Infer a -> Either TypeError a
runInfer env m = runExcept $ evalStateT (runReaderT m env) initInfer
-- | Solve for the toplevel type of an expression in a given environment
inferTerm :: InterpreterEnv -> SimpleTerm -> Either TypeError Scheme
inferTerm env ex = case runInfer env (infer ex) of
Left err -> Left err
Right (ty, ps, cs) -> case runSolve cs of
Left err -> Left err
Right subst -> case runInfer env (filterAndCheck (apply subst ps) (apply subst ty)) of
Left err -> Left err
Right xs -> Right $ closeOver xs $ apply subst ty
-- | Return the internal constraints used in solving for the type of an expression
-- constraintsTerm :: Env -> SimpleTerm -> Either TypeError ([Constraint], Subst, Type, Scheme)
-- constraintsTerm env ex = case runInfer env (infer ex) of
-- Left err -> Left err
-- Right (ty, cs) -> case runSolve cs of
-- Left err -> Left err
-- Right subst -> Right (cs, subst, ty, sc)
-- where
-- sc = closeOver $ apply subst ty
-- | Canonicalize and return the polymorphic toplevel type.
closeOver :: [Predicate] -> Type -> Scheme
closeOver ps t = normalize $ generalize ps t
-- | modified environment where x :: sc
inEnv :: (Name, Scheme) -> Infer a -> Infer a
inEnv (x, sc) m = do
let scope = insertType x sc
local scope m
-- | Lookup type in the environment
lookupEnv :: Name -> Infer (Type, [Predicate])
lookupEnv x = do
env <- ask
case lookupType x env of
Nothing -> throwError $ UnboundVariable x
Just s -> instantiate s
letters :: [Text]
letters = map pack $ [1 ..] >>= flip replicateM ['a' .. 'z']
fresh :: Infer Type
fresh = do
s <- get
put s {count = count s + 1}
return $ TypeVar (letters !! count s)
instantiate :: Scheme -> Infer (Type, [Predicate])
instantiate (Forall as (Qual ps t)) = do
as' <- mapM (const fresh) as
let s = Subst $ Map.fromList $ zip as as'
return $ (apply s t, apply s ps)
generalize :: [Predicate] -> Type -> Scheme
generalize ps t = Forall as (Qual ps t)
where
as = Set.toList $ ftv t
filterAndCheck :: [Predicate] -> Type -> Infer [Predicate]
filterAndCheck [] _ = return []
filterAndCheck (p@(IsIn _ (TypeVar _)) : ps) t =
if or $ Set.map (\x -> elem x $ ftv p) (ftv t)
then (p :) <$> filterAndCheck ps t
else filterAndCheck ps t
filterAndCheck (p : ps) t = checkInstance p >> filterAndCheck ps t
checkInstance :: Predicate -> Infer ()
checkInstance p = do
(IEnv _ is) <- ask
(if p `elem` is then return () else throwError $ NoInstance p)
infer :: SimpleTerm -> Infer (Type, [Predicate], [Constraint])
infer expr = case expr of
SVar _ x -> do
(t, ps) <- lookupEnv x
return (t, ps, [])
SText _ _ -> return (textT, [], [])
SNum _ _ -> return (numberT, [], [])
SBracket s -> infer s
SRest -> do
tv <- fresh
return (tv, [], [])
SLambda x e -> do
tv <- fresh
(t, ps, c) <- inEnv (x, Forall [] (Qual [] tv)) (infer e)
return (tv `TypeArr` t, ps, c)
SApp e1 e2 -> do
(t1, ps1, c1) <- infer e1
(t2, ps2, c2) <- infer e2
tv <- fresh
return (tv, ps1 ++ ps2, c1 ++ c2 ++ [(t1, t2 `TypeArr` tv)])
SInfix e1 op e2 -> do
(t1, ps1, c1) <- infer e1
(t2, ps2, c2) <- infer e2
tv <- fresh
let u1 = t1 `TypeArr` (t2 `TypeArr` tv)
(u2, p3) <- lookupEnv op
return (tv, ps1 ++ ps2 ++ p3, c1 ++ c2 ++ [(u1, u2)])
SSeq (x : xs) -> do
(t, ps, cs) <- infer x
infs <- mapM infer xs
return (t, ps, cs ++ concatMap (\(_, _, y) -> y) infs ++ [(t, t') | t' <- map (\(y, _, _) -> y) infs])
SStack (x : xs) -> do
(t, ps, cs) <- infer x
infs <- mapM infer xs
return (t, ps, cs ++ concatMap (\(_, _, y) -> y) infs ++ [(t, t') | t' <- map (\(y, _, _) -> y) infs])
SChoice _ (x : xs) -> do
(t, ps, cs) <- infer x
infs <- mapM infer xs
return (t, ps, cs ++ concatMap (\(_, _, y) -> y) infs ++ [(t, t') | t' <- map (\(y, _, _) -> y) infs])
_ -> error "Can't happen"
normalize :: Scheme -> Scheme
normalize (Forall _ (Qual ps body)) = Forall (map snd ord) (Qual (map normpred ps) $ normtype body)
where
ord = zip (nub $ fv body) letters
fv (TypeVar a) = [a]
fv (TypeArr a b) = fv a ++ fv b
fv (TypeCon _) = []
normtype (TypeArr a b) = TypeArr (normtype a) (normtype b)
normtype (TypeCon a) = TypeCon a
normtype (TypeVar a) =
case Prelude.lookup a ord of
Just x -> TypeVar x
Nothing -> error "type variable not in signature"
normpred (IsIn n t) = IsIn n (normtype t)