Implemented the hindley-milner algorithm for STLC
This commit is contained in:
commit
e54697f0f4
8 changed files with 301 additions and 0 deletions
23
.gitignore
vendored
Normal file
23
.gitignore
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
dist
|
||||
dist-*
|
||||
cabal-dev
|
||||
*.o
|
||||
*.hi
|
||||
*.hie
|
||||
*.chi
|
||||
*.chs.h
|
||||
*.dyn_o
|
||||
*.dyn_hi
|
||||
.hpc
|
||||
.hsenv
|
||||
.cabal-sandbox/
|
||||
cabal.sandbox.config
|
||||
*.prof
|
||||
*.aux
|
||||
*.hp
|
||||
*.eventlog
|
||||
.stack-work/
|
||||
cabal.project.local
|
||||
cabal.project.local~
|
||||
.HTF/
|
||||
.ghc.environment.*
|
20
LICENSE
Normal file
20
LICENSE
Normal file
|
@ -0,0 +1,20 @@
|
|||
Copyright (c) 2023 Leon Vatthauer
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
36
app/Main.hs
Normal file
36
app/Main.hs
Normal file
|
@ -0,0 +1,36 @@
|
|||
module Main where
|
||||
|
||||
import qualified Data.Map.Strict as Map
|
||||
|
||||
import Types ( TermInContext, Type(Arrow, BaseType), emptyContext )
|
||||
import Terms ( Term(..) )
|
||||
import TypeInference ( inferType )
|
||||
|
||||
-- some base types
|
||||
int :: Type
|
||||
int = BaseType "int"
|
||||
string :: Type
|
||||
string = BaseType "string"
|
||||
|
||||
-- ThProg: sheet 07, exercise 2, 1.
|
||||
term1 :: Term
|
||||
term1 = Abs "x" $ Abs "y" $ Abs "z" $ App (Var "x") (App (Var "y") (Var "z"))
|
||||
exercise1 :: Maybe TermInContext
|
||||
exercise1 = inferType emptyContext term1
|
||||
|
||||
-- ThProg: sheet 07, exercise 2, 2.
|
||||
term2 :: Term
|
||||
term2 = Abs "x" $ App (Var "add") (App (Var "length") (Var "x"))
|
||||
exercise2 :: Maybe TermInContext
|
||||
exercise2 = inferType (Map.fromList [("add", Arrow int (Arrow int int)), ("length", Arrow string int)]) term2
|
||||
|
||||
omega :: Term
|
||||
omega = Abs "x" $ App (Var "x") (Var "x")
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
print exercise1
|
||||
print exercise2
|
||||
|
||||
putStrLn "Trying to type '\\x.xx' in empty context:"
|
||||
print $ inferType emptyContext omega
|
17
app/Terms.hs
Normal file
17
app/Terms.hs
Normal file
|
@ -0,0 +1,17 @@
|
|||
{-# LANGUAGE InstanceSigs #-}
|
||||
module Terms where
|
||||
|
||||
-- Lambda-Terms
|
||||
data Term
|
||||
= Var String
|
||||
| App Term Term
|
||||
| Abs String Term
|
||||
deriving (Eq, Ord)
|
||||
instance Show Term where
|
||||
show :: Term -> String
|
||||
show (Var x) = x
|
||||
show (App t1 t2) = show t1 ++ " " ++ go t2
|
||||
where
|
||||
go (App t3 t4) = "(" ++ show t3 ++ " " ++ go t4 ++ ")"
|
||||
go t = show t
|
||||
show (Abs x t) = "\\" ++ x ++ ". " ++ show t
|
44
app/TypeInference.hs
Normal file
44
app/TypeInference.hs
Normal file
|
@ -0,0 +1,44 @@
|
|||
module TypeInference where
|
||||
|
||||
import Control.Monad.State.Strict
|
||||
( gets, modify, evalState, State )
|
||||
import qualified Data.Map.Strict as Map
|
||||
|
||||
import Terms ( Term(..) )
|
||||
import Types ( TermInContext(..), Context, Type(TypeVar, Arrow) )
|
||||
import Unification ( Unifier, unify, applyMgu )
|
||||
|
||||
-- state needed for algorithm W
|
||||
newtype PTState = PTState {
|
||||
varCount :: Int
|
||||
}
|
||||
type PTMonad = State PTState
|
||||
|
||||
-- returns a fresh type var in algorithm W
|
||||
getNewTypeVar :: PTMonad String
|
||||
getNewTypeVar = do
|
||||
count <- gets varCount
|
||||
modify (\s -> s {varCount = count + 1})
|
||||
return $ 'a' : show count
|
||||
|
||||
-- implementation of algorithm W, this builds a list of terms to be unified in the next step
|
||||
pt :: Context -> Term -> Type -> PTMonad Unifier
|
||||
pt gamma (Var x) alpha = return [(alpha, gamma Map.! x)]
|
||||
pt gamma (Abs x t) alpha = do
|
||||
a <- TypeVar <$> getNewTypeVar
|
||||
b <- TypeVar <$> getNewTypeVar
|
||||
result <- pt (Map.insert x a gamma) t b
|
||||
return $ result ++ [(Arrow a b, alpha)]
|
||||
pt gamma (App t s) alpha = do
|
||||
a <- TypeVar <$> getNewTypeVar
|
||||
result1 <- pt gamma t (Arrow a alpha)
|
||||
result2 <- pt gamma s a
|
||||
return $ result1 ++ result2
|
||||
|
||||
-- infer the type of a term in context (if any)
|
||||
inferType :: Context -> Term -> Maybe TermInContext
|
||||
inferType ctx term = do
|
||||
let unificationProblem = evalState (pt ctx term (TypeVar "a0")) (PTState 1)
|
||||
mgu <- unify unificationProblem
|
||||
let principalType = applyMgu (TypeVar "a0") mgu
|
||||
return $ TermInContext ctx term principalType
|
49
app/Types.hs
Normal file
49
app/Types.hs
Normal file
|
@ -0,0 +1,49 @@
|
|||
{-# LANGUAGE InstanceSigs #-}
|
||||
module Types where
|
||||
|
||||
import Data.Map (Map)
|
||||
import qualified Data.Map.Strict as Map
|
||||
import Data.List ( intercalate )
|
||||
|
||||
import Terms ( Term )
|
||||
|
||||
-- Types
|
||||
data Type
|
||||
= TypeVar String
|
||||
| BaseType String
|
||||
| Arrow Type Type
|
||||
deriving (Eq, Ord)
|
||||
instance Show Type where
|
||||
show :: Type -> String
|
||||
show (TypeVar v) = v
|
||||
show (BaseType b) = b
|
||||
show (Arrow t1 t2) = go t1 ++ " -> " ++ show t2
|
||||
where
|
||||
go (Arrow t3 t4) = "(" ++ go t3 ++ " -> " ++ show t4 ++ ")"
|
||||
go t = show t
|
||||
|
||||
-- type variables occuring in type
|
||||
typeVars :: Type -> [String]
|
||||
typeVars (TypeVar v) = [v]
|
||||
typeVars (BaseType _) = []
|
||||
typeVars (Arrow t1 t2) = typeVars t1 ++ typeVars t2
|
||||
|
||||
-- substitute a type for a type variable
|
||||
typeSubst :: Type -> String -> Type -> Type
|
||||
typeSubst tp@(TypeVar x) y sp = if x == y then sp else tp
|
||||
typeSubst tp@(BaseType _) _ _ = tp
|
||||
typeSubst (Arrow tp1 tp2) y tp3 = Arrow (typeSubst tp1 y tp3) (typeSubst tp2 y tp3)
|
||||
|
||||
-- Contexts
|
||||
type Context = Map String Type
|
||||
showContext :: Context -> String
|
||||
showContext ctx | Map.null ctx = ""
|
||||
showContext ctx = "[" ++ intercalate ", " (map (\(name, tp) -> name ++ " : " ++ show tp) $ Map.toList ctx) ++ "]"
|
||||
emptyContext :: Context
|
||||
emptyContext = Map.empty
|
||||
|
||||
-- Term in Context
|
||||
data TermInContext = TermInContext Context Term Type
|
||||
instance Show TermInContext where
|
||||
show :: TermInContext -> String
|
||||
show (TermInContext ctx term tp) = showContext ctx ++ " |- " ++ show term ++ " : " ++ show tp
|
35
app/Unification.hs
Normal file
35
app/Unification.hs
Normal file
|
@ -0,0 +1,35 @@
|
|||
module Unification where
|
||||
|
||||
import Types ( Type(..), typeVars, typeSubst )
|
||||
|
||||
type Unifier = [(Type, Type)]
|
||||
|
||||
-- unification algorithm of martelli montanari
|
||||
unify :: Unifier -> Maybe Unifier
|
||||
unify [] = Just []
|
||||
-- (delete)
|
||||
unify ((TypeVar x, TypeVar y) : rest) | x == y = unify rest
|
||||
-- (decomp)
|
||||
unify ((Arrow t1 t2, Arrow t1' t2') : rest) = unify $ [(t1, t1'), (t2, t2')] ++ rest
|
||||
unify ((BaseType b1, BaseType b2) : rest) | b1 == b2 = unify rest
|
||||
-- (conflict)
|
||||
unify ((BaseType b1, BaseType b2) : _) | b1 /= b2 = Nothing
|
||||
-- (orient)
|
||||
unify ((Arrow t1 t2, TypeVar x) : rest) = unify $ (TypeVar x, Arrow t1 t2) : rest
|
||||
unify ((BaseType b, TypeVar x) : rest) = unify $ (TypeVar x, BaseType b) : rest
|
||||
-- (occurs)
|
||||
unify ((TypeVar x, t) : _) | x `elem` typeVars t && TypeVar x /= t = Nothing
|
||||
-- (elim)
|
||||
unify ((TypeVar x, t) : rest) | notElem x (typeVars t) && x `elem` concatMap (\(t1, t2) -> typeVars t1 ++ typeVars t2) rest = unify $ (TypeVar x, t) : map (\(t1, t2) -> (typeSubst t1 x t, typeSubst t2 x t)) rest
|
||||
-- descent
|
||||
unify ((t, s) : rest) = do
|
||||
rest' <- unify rest
|
||||
return $ (t, s) : rest'
|
||||
|
||||
-- apply a mgu to some type
|
||||
applyMgu :: Type -> Unifier -> Type
|
||||
applyMgu (TypeVar x) [] = TypeVar x
|
||||
applyMgu (TypeVar x) ((TypeVar y, tp) : rest) = if x == y then tp else applyMgu (TypeVar x) rest
|
||||
applyMgu (BaseType b) _ = BaseType b
|
||||
applyMgu (Arrow t1 t2) mgu = Arrow (applyMgu t1 mgu) (applyMgu t2 mgu)
|
||||
applyMgu _ (tp : _) = error $ "Non primitive type '" ++ show tp ++ "' on left side of mgu"
|
77
type-inference.cabal
Normal file
77
type-inference.cabal
Normal file
|
@ -0,0 +1,77 @@
|
|||
cabal-version: 3.0
|
||||
-- The cabal-version field refers to the version of the .cabal specification,
|
||||
-- and can be different from the cabal-install (the tool) version and the
|
||||
-- Cabal (the library) version you are using. As such, the Cabal (the library)
|
||||
-- version used must be equal or greater than the version stated in this field.
|
||||
-- Starting from the specification version 2.2, the cabal-version field must be
|
||||
-- the first thing in the cabal file.
|
||||
|
||||
-- Initial package description 'type-inference' generated by
|
||||
-- 'cabal init'. For further documentation, see:
|
||||
-- http://haskell.org/cabal/users-guide/
|
||||
--
|
||||
-- The name of the package.
|
||||
name: type-inference
|
||||
|
||||
-- The package version.
|
||||
-- See the Haskell package versioning policy (PVP) for standards
|
||||
-- guiding when and how versions should be incremented.
|
||||
-- https://pvp.haskell.org
|
||||
-- PVP summary: +-+------- breaking API changes
|
||||
-- | | +----- non-breaking API additions
|
||||
-- | | | +--- code changes with no API change
|
||||
version: 0.1.0.0
|
||||
|
||||
-- A short (one-line) description of the package.
|
||||
synopsis:
|
||||
A small implementation of the hindley milner type inference algorithm (Algorithm W)
|
||||
|
||||
-- A longer description of the package.
|
||||
-- description:
|
||||
|
||||
-- The license under which the package is released.
|
||||
license: MIT
|
||||
|
||||
-- The file containing the license text.
|
||||
license-file: LICENSE
|
||||
|
||||
-- The package author(s).
|
||||
author: Leon Vatthauer
|
||||
|
||||
-- An email address to which users can send suggestions, bug reports, and patches.
|
||||
-- maintainer:
|
||||
|
||||
-- A copyright notice.
|
||||
-- copyright:
|
||||
category: Language
|
||||
build-type: Simple
|
||||
|
||||
-- Extra source files to be distributed with the package, such as examples, or a tutorial module.
|
||||
-- extra-source-files:
|
||||
|
||||
common warnings
|
||||
ghc-options: -Wall
|
||||
|
||||
executable type-inference
|
||||
-- Import common warning flags.
|
||||
import: warnings
|
||||
|
||||
-- .hs or .lhs file containing the Main module.
|
||||
main-is: Main.hs
|
||||
|
||||
-- Modules included in this executable, other than Main.
|
||||
-- other-modules:
|
||||
|
||||
-- LANGUAGE extensions used by modules in this package.
|
||||
-- other-extensions:
|
||||
|
||||
-- Other library packages from which modules are imported.
|
||||
build-depends: base ^>=4.16.4.0, containers, mtl
|
||||
|
||||
-- Directories containing source files.
|
||||
hs-source-dirs: app
|
||||
|
||||
other-modules: Terms, Types, Unification, TypeInference
|
||||
|
||||
-- Base language which the package is written in.
|
||||
default-language: Haskell2010
|
Loading…
Reference in a new issue