Haskell for Lambda Calculus, Type Inferencing
functional-programming, haskell, lambda, lambda-calculus, types
Solution
The key thing to take away from the simply typed lambda calculus is that the types are annotated on the lambda binders itself, every lambda term has a type. The typing rules that Pierce provides are are how to mechanically type-check that the expression is well-typed. type inference is a topic he covers later in the book, which is recovering the types from untyped expressions.
Aside, what Pierce doesn't give in this example is a couple ground types (`Bool`, `Int`) , which are helpful when implementing the algorithm, so we'll just append those to our definition as well.
t = x
| λ x : T . t
| t t
| <num>
| true
| false
T = T -> T
| TInt
| TBool
If we translate this into Haskell we get:
type Sym = String
data Expr
= Var Sym
| Lam Sym Type Expr
| App Expr Expr
| Lit Ground
deriving (Show, Eq, Ord)
data Ground = LInt Int
| LBool Bool
deriving (Show, Eq, Ord)
data Type = TInt
| TBool
| TArr Type Type
deriving (Eq, Read, Show)
The `Γ` that pierce threads through the equations is for type environment which we can represent in Haskell as a simple list structure.
type Env = [(Sym, Type)]
The empty environment `Ø` is then simply `[]`. When Pierce writes `Γ, x : T ⊢ ...` he means the environment extended with the definition of `x` bound to the type `T`. In Haskell we would implement it like:
extend :: Env -> (Sym, Type) -> Env
extend env xt = xt : env
To write the checker from TAPL we implement a little error monad stack.
data TypeError = Err String deriving Show
instance Error TypeError where
noMsg = Err ""
type Check a = ErrorT TypeError Identity a
check :: Env -> Expr -> Check Type
check _ (Lit LInt{}) = return TInt
check _ (Lit LBool{}) = return TBool
-- x : T ∈ Γ
-- ----------
-- Γ ⊦ x : T
check env (Var x) = case (lookup x env) of
Just e -> return e
Nothing -> throwError $ Err "Not in Scope"
-- Γ, x : T ⊦ e : T'
-- --------------------
-- Γ ⊦ λ x . e : T → T'
check env (Lam x t e) = do
rhs <- (check (extend env (x,t)) e)
return (TArr t rhs)
-- Γ ⊦ e1 : T → T' Γ ⊦ e2 : T
-- ----------------------------
-- Γ ⊦ e1 e2 : T'
check env (App e1 e2) = do
t1 <- check env e1
t2 <- check env e2
case t1 of
(TArr t1a t1r) | t1a == t2 -> return t1r
(TArr t1a _) -> throwError $ Err "Type mismatch"
ty -> throwError $ Err "Trying to apply non-function"
runCheck :: Check a -> Either TypeError a
runCheck = runIdentity . runErrorT
checkExpr :: Expr -> Either TypeError Type
checkExpr x = runCheck $ check [] x
When we call `checkExpr` on a expression we either get back the valid type of the expression or a `TypeError` indicating what is wrong with the function.
For instance if we have the term:
(λx : Int -> Int . x) (λy : Int. y) 3
App (App (Lam "x" (TArr TInt TInt) (Var "x")) (Lam "y" TInt (Var "y"))) (Lit (LInt 3))
We expect our type checker to validate that it it has output type `TInt`.
But to fail for a term like:
(λx : Int -> Int . x) 3
App (Lam "x" (TArr TInt TInt) (Var "x")) (Lit (LInt 3))
Since `TInt` is not equal to `(TInt -> TInt)`.
That's all there really is to typechecking the STLC.
Problem
My adventure in Haskell programming hasn't been all epic. I am implementing Simple Lambda Calculus, and I am glad to have finished `Syntax`, `Evaluation`, as well as `Substitution`, hoping they are correct. What remains is `typing` as defined inside the red box (in the figure below), for which I am looking for guidance. Heading Please correct me if I am wrong, (1) but what I gathered is that `(T-Var)`, returns the type of given a variable `x`. What construct in Haskell returns `type` ? I know that in `prelude` it is `:t x`, but I am looking for one that works under `main = do`. (2) If I were to define a function `type_of`, it's most likely that I need to define the expected and return type, as an example, `type_of (Var x) :: type1 -> type2` `type1` should be generic and `type2` must be whatever object type that stores type information of a variable. For this, I am lost on how to define `type1` and `type2`. (3) For (T-APP) and (T-ABS), I assume I apply substitution on `Abstraction String Lambda` and `Application Lambda Lambda` respectively. The type of the reduced form is the returned type. Is that correct? Thanks in advance...