随着对函数式语言的兴趣日渐浓厚,我对设计一门编程语言的冲动也越来越强烈。这个过程非常的艰难,需要大量的基础知识作为铺垫,其中非常重要的部分——类型系统尤为复杂。我从一些博文中了解到了Hindley-Milner类型系统、Algorithm W 和 Algorithm J等等知识,并试图整理出来一条脉络以供日后使用。
{- Expressions -} abc-- Variable if a then b else c -- If-expression \x -> y : a -> b -- Abstraction f x -- Application let a = b in c -- Let-binding 123456789 : Int-- Integer True, False : Bool-- Boolean x + y -- Integer addition x - y -- Integer subtraction
如果写着 ,这代表 是 类型
Syntax Representation
定义一些数据结构来表示我们设计的语言(启用 PatternSynoyms 拓展)。
首先给出一个类型别名用来表示符号名
{-# Language PatternSynonyms #-} typeName = String
当推断 id 的类型时,它被推断为 id : a -> a。然而,在 let 绑定的主体中,我们将其应用于一个布尔值。由于我们没有明确指出 id 应该是多态的,因此 id 的最终推断类型将变为 id : Bool -> Bool。而Scheme解决了这个问题,并推断出 id : \forall a. a -> a。
instanceSubstitutableTypewhere tvs (TVar tv) = Set.singleton tv tvs (TCon _ ts) = foldr (Set.union . tvs) Set.empty ts apply s t@(TVar tv) = Map.findWithDefault t tv s apply s (TCon c ts) = TCon c $ map (apply s) ts
instanceSubstitutableSchemewhere tvs (Forall vs t) = tvs t `Set.difference` vs apply s (Forall vs t) = Forall vs $ apply (foldr Map.delete s vs) t instanceSubstitutableConstraintwhere tvs (Constraint t1 t2) = tvs t1 `Set.union` tvs t2 apply s (Constraint t1 t2) = Constraint (apply s t1) (apply s t2) instanceSubstitutable a => Substitutable [a] where tvs l = foldr (Set.union . tvs) Set.empty l apply s = map (apply s)
generalize :: Context -> Type -> Scheme generalize ctx t = Forall (tvs t `Set.difference` tvs (Map.elems ctx)) t
instantiate :: Scheme -> InferType instantiate (Forall vs t) = do let vars = Set.toList vs ftvs <- traverse (const fresh) vars let subst = Map.fromList (zip vars ftvs) return $ apply subst t
现在我们可以开始主要的表达式推断函数。它的类型将是 Expr -> Infer Type。这个函数所做的只是逆向通过我们之前讨论过的推断规则(如果需要,请回顾第一部分)。对于 if 表达式的推断解释已经在注释中说明,尽管它应该是相当明显的。
infer :: Expr -> InferType infer = \case EInt _ -> TInt-- Integer literal EBool _ -> TBool-- Boolean literal EVar v -> do ctx <- ask -- Retrieve context from Reader caseMap.lookup v ctx of Just t -> instantiate t -- Instantiate type scheme for use Nothing -> throwError $ "Undefined variable " ++ v -- Variable not defined EIf c a b -> do ct <- infer c -- Infer type of condition expression at <- infer a -- Infer type of main branch bt <- infer b -- Infer type of else branch constrain ct TBool-- Condition expression should be a Bool constrain at bt -- Branches should be of same type return at -- Return type of any branch EAbs p e -> do pt <- fresh -- Generate fresh type variable for param let ps = ForallSet.empty pt et <- local (Map.insert p ps) (infer e) -- Infer function definition with param defined return $ pt :-> et -- Function has type pt -> et EApp f a -> do ft <- infer f -- Infer type of expression being called at <- infer a -- Infer type of argument rt <- fresh -- Fresh type variable for result type constrain ft (at :-> rt) return rt EBin o a b -> do let ot = -- Operators are functions case o of Add -> TInt :-> (TInt :-> TInt) Sub -> TInt :-> (TInt :-> TInt) -- NOTE: ADD MORE OPERATORS!! at <- infer a -- Infer left operand bt <- infer b -- Infer right operand t <- fresh -- Result type constrain ot (at :-> (bt :-> t)) return t ELet v e b -> do et <- infer e -- Infer variable type ctx <- ask let es = generalize ctx et -- Generalize variable type bt <- local (Map.insert v es) (infer b) -- Infer body with variable defined return bt
unify :: Type -> Type -> SolveSubst unify a b | a == b = return Map.empty -- Same unify (TVar v) t = bind v t -- Var Left unify t (TVar v) = bind v t -- Var Right unify a@(TCon n1 ts1) b@(TCon n2 ts2) -- Arrow (->) / other TCons | a /= b = throwError $ "Type mismatch " ++ show a ++ " and " ++ show b | otherwise = unifyMany ts1 ts2 where unifyMany [] [] = return Map.empty unifyMany (t1 : ts1) (t2 : ts2) = do s1 <- unify t1 t2 s2 <- unifyMany (apply s1 ts1) (apply s1 ts2) return (s2 `compose` s1)
bind :: TVar -> Type -> SolveSubst bind v t | v `Set.member` tvs t = throwError $ "Infinite type " ++ show v ++ " ~ " ++ show t -- Occurs check | otherwise = return $ Map.singleton v t