typerbole-0.0.0.1: src/Compiler/Typesystem/SimplyTyped.hs
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-}
module Compiler.Typesystem.SimplyTyped where
import Calculi.Lambda
import Calculi.Lambda.Cube.SimpleType
import Control.Typecheckable
import Control.Monad
import qualified Control.Monad.State as State
import Data.Bifunctor
import Data.Generics
import qualified Data.Map as Map
import Data.Random.Generics
import Data.Semigroup
import qualified Data.Set as Set
import Test.QuickCheck
import qualified Language.Haskell.TH.Lift as TH
{-|
Data type describing a type system for simply-typed lambda calculus (λ→).
-}
data SimplyTyped m =
Mono m
| Function (SimplyTyped m) (SimplyTyped m)
deriving (Show, Read, Eq, Ord, Data)
TH.deriveLift ''SimplyTyped
instance Functor SimplyTyped where
fmap f = \case
Mono m -> Mono (f m)
Function from to -> Function (fmap f from) (fmap f to)
instance Foldable SimplyTyped where
foldr f z = \case
Mono m -> f m z
Function from to -> foldr f (foldr f z to) from
instance Ord m => SimpleType (SimplyTyped m) where
type MonoType (SimplyTyped m) = m
abstract = Function
reify (Function from to) = Just (from, to)
reify _ = Nothing
bases (Mono m) = Set.singleton (Mono m)
bases (Function from to) = bases from `Set.union` bases to
mono = Mono
equivalent = (==)
data SimplyTypedErr c v t =
STNotKnownErr (NotKnownErr c v t)
| STSimpleTypeErr (SimpleTypeErr t)
deriving (Eq, Ord, Show)
instance (Ord m, Ord c, Ord v) => Typecheckable (LambdaTerm c v) (SimplyTyped m) where
type TypingContext (LambdaTerm c v) (SimplyTyped m) = (SimpleTypingContext c v (SimplyTyped m))
type TypeError (LambdaTerm c v) (SimplyTyped m) =
-- Using an ErrorContext because it can hold a lot of information.
ErrorContext'
(LambdaTerm c v)
(SimplyTyped m) (SimplyTypedErr c v (SimplyTyped m))
typecheck env _expr =
-- Always add the current expression to all the errors that have occurred
-- so there's a list of expressions towards the error that happened.
first (fmap (\err -> err { _expression = _expr : _expression err }))
$ case _expr of
{-
Case of a LC variable, not really typechecking but instead
checking that the variable name is in scope.
-}
Variable v ->
let -- If there's an error on the var lookup, return this as the error.
err = Left [ErrorContext [] env (STNotKnownErr (UnknownVariable v))]
-- If it succeeds in it's lookup, pipe the result into a tuple in a Right value.
success = (Right . (,) env)
in maybe err success (Map.lookup v (_variables env))
{-
Application of two expressions.
-}
(Apply fun arg) ->
let -- Typecheck both the function side and the argument
fun' = typecheck env fun
arg' = typecheck env arg
in case (fun', arg') of
-- If both errored, then combine the errors.
(Left fun'err, Left arg'err) -> Left $ fun'err <> arg'err
_ -> do
-- discard their contexts
(_, fun'type) <- fun'
(_, arg'type) <- arg'
case reify fun'type of
Nothing ->
-- The first expression's type wasn't a function type.
Left [ErrorContext [fun] env (STSimpleTypeErr $ NotAFunction fun'type)]
Just (funarg'type, funret'type)
| funarg'type /= arg'type ->
-- The type of the argument expression doesn't match the expected
-- type of the function expression.
Left [ErrorContext [fun] env (STSimpleTypeErr $ UnexpectedType funarg'type arg'type)]
| otherwise ->
-- succeeded in typechecking.
Right (env, funret'type)
{-
Abstracting a variable over an expression.
-}
(Lambda (var, var'type) expr) -> do
-- Get any types in var'type not in the set of all types
-- as every type should be declared by the environment
-- before appearing in an expression.
let unknownTypes = Set.toList $ Set.difference (bases var'type) (_allTypes env)
let errFun t = ErrorContext [] env (STNotKnownErr$ UnknownType t)
-- If there are unknown types, error on them.
unless (null unknownTypes) . Left $ errFun <$> unknownTypes
-- If we get ths far, then var'type was valid
-- Set a new typing context with var brought into scope
let env' = env { _variables = Map.insert var var'type (_variables env) }
-- Typecheck the lambda's body
(_, expr'type) <- typecheck env' expr
-- Return the environment and the type of the lambda expression.
return (env, var'type /-> expr'type)
instance (Data m, Arbitrary m) => Arbitrary (SimplyTyped m) where
arbitrary = sized $ generatorPWith [alias (\() -> arbitrary :: Gen m)]