├── README.md ├── sywtwatc.hs └── sywtwatc.js /README.md: -------------------------------------------------------------------------------- 1 | so-you-want-to-write-a-type-checker 2 | =================================== 3 | 4 | Code accompanying the blog post http://languagengine.co/blog/so-you-want-to-write-a-type-checker/ 5 | -------------------------------------------------------------------------------- /sywtwatc.hs: -------------------------------------------------------------------------------- 1 | -- Because this uses Haskell, and Haskell has a nice type system, 2 | -- we don't need judgment_type at all. The type we're going to use 3 | -- here to represent types guarantees that what we would've done 4 | -- with judgment_type is handled. We also don't need the type_equality 5 | -- function because Haskell can derive the correct equality. 6 | 7 | data Type = Foo | Bar | Baz 8 | | Type :*: Type 9 | | Type :->: Type 10 | deriving (Eq) 11 | 12 | 13 | -- We still need judgment_ctx (if we decide to use it) because it does 14 | -- more than simply assure the right overall form: it also checks that 15 | -- the variables are all distinct. So this type is only part of it. 16 | 17 | data Context = Empty | Snoc Context String Type 18 | 19 | 20 | not_in :: String -> Context -> Bool 21 | not_in n Empty = True 22 | not_in n (Snoc g n' _) 23 | | n == n' = False 24 | | otherwise = not_in n g 25 | 26 | 27 | judgment_ctx :: Context -> Bool 28 | judgment_ctx Empty = True 29 | judgment_ctx (Snoc g n a) = judgment_ctx g 30 | && not_in n g 31 | 32 | 33 | data Term = Pair Term Term | Split Term String Type String Type Term 34 | | Lam String Term | App Term Term Type 35 | | Var String 36 | 37 | 38 | var_has_type :: String -> Type -> Context -> Bool 39 | var_has_type n a Empty = False 40 | var_has_type n a (Snoc g n' a') 41 | | n == n' = a == a' 42 | | otherwise = var_has_type n a g 43 | 44 | 45 | judgment_check :: Context -> Term -> Type -> Bool 46 | judgment_check g (Pair m n) (a :*: b) = judgment_check g m a 47 | && judgment_check g n b 48 | 49 | judgment_check g (Split p x a y b m) c = judgment_check g p (a :*: b) 50 | && judgment_check (Snoc (Snoc g x a) 51 | y b) 52 | m 53 | c 54 | 55 | judgment_check g (Lam x m) (a :->: b) = judgment_check (Snoc g x a) m b 56 | 57 | judgment_check g (App m n a) b = judgment_check g m (a :->: b) 58 | && judgment_check g n a 59 | 60 | judgment_check g (Var x) a = var_has_type x a g 61 | -------------------------------------------------------------------------------- /sywtwatc.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Making representations of types 3 | */ 4 | 5 | var Foo = { tag: "Foo" }; 6 | var Bar = { tag: "Bar" }; 7 | var Baz = { tag: "Baz" }; 8 | 9 | function prod(a,b) { 10 | return { tag: "*", left: a, right: b }; 11 | } 12 | 13 | function arr(a,b) { 14 | return { tag: "->", arg: a, ret: b }; 15 | } 16 | 17 | 18 | /* 19 | * A type 20 | * ====== 21 | */ 22 | function judgment_type(a) { 23 | 24 | /* -------- Foo Formation 25 | * Foo type 26 | * 27 | * -------- Bar Formation 28 | * Bar type 29 | * 30 | * -------- Baz Formation 31 | * Baz type 32 | */ 33 | if ("Foo" == a.tag || "Bar" == a.tag || "Baz" == a.tag) { 34 | 35 | return true; 36 | 37 | } 38 | 39 | /* A type B type 40 | * ---------------- * Formation 41 | * A*B type 42 | */ 43 | else if ("*" == a.tag) { 44 | 45 | return judgment_type(a.left) && judgment_type(a.right); 46 | 47 | } 48 | 49 | /* A type B type 50 | * ---------------- -> Formation 51 | * A -> B type 52 | */ 53 | else if ("->" == a.tag) { 54 | 55 | return judgment_type(a.arg) && judgment_type(a.ret); 56 | 57 | } 58 | 59 | /* 60 | * Nothing else is a type 61 | */ 62 | else { 63 | 64 | return false; 65 | 66 | } 67 | } 68 | 69 | 70 | 71 | /* 72 | * Making representations of contexts 73 | */ 74 | 75 | var empty = { tag: "<>" } 76 | 77 | function snoc(g,x,a) { 78 | return { tag: ",:", rest: g, name: x, type: a }; 79 | } 80 | 81 | /* 82 | * Testing whether or not a variable is in a context 83 | */ 84 | function not_in(n, g) { 85 | if ("<>" == g.tag) { 86 | 87 | return true; 88 | 89 | } else { 90 | 91 | if (n == g.name) { 92 | 93 | return false; 94 | 95 | } else { 96 | 97 | return not_in(n, g.rest); 98 | 99 | } 100 | 101 | } 102 | } 103 | 104 | 105 | /* 106 | * G ctx 107 | * ===== 108 | */ 109 | function judgment_ctx(g) { 110 | 111 | /* 112 | * ------ empty context 113 | * <> ctx 114 | */ 115 | if ("<>" == g.tag) { 116 | 117 | return true; 118 | 119 | } 120 | 121 | /* 122 | * G ctx A type x is not in G 123 | * -------------------------------- new var 124 | * G, x : A ctx 125 | */ 126 | else if (",:" == g.tag) { 127 | 128 | return judgment_ctx(g.rest) && 129 | judgment_type(g.type) && 130 | not_in(g.name, g.rest); 131 | 132 | } 133 | 134 | /* 135 | * Nothing else is a context 136 | */ 137 | else { 138 | 139 | return false; 140 | 141 | } 142 | } 143 | 144 | 145 | 146 | 147 | /* 148 | * Making representations of terms 149 | */ 150 | 151 | function pair(m,n) { 152 | return { tag: "(,)", first: m, second: n }; 153 | } 154 | 155 | function split(p, x, a, y, b, m) { 156 | return { tag: "split", 157 | pair: p, 158 | name_x: x, type_a: a, 159 | name_y: y, type_b: b, 160 | body: m }; 161 | } 162 | 163 | function lam(x,m) { 164 | return { tag: "lam", name: x, body: m }; 165 | } 166 | 167 | function app(m,n,a) { 168 | return { tag: "app", fun: m, arg: n, type_arg: a }; 169 | } 170 | 171 | function v(n) { 172 | return { tag: "variable", name: n }; 173 | } 174 | 175 | /* 176 | * Checking if two types are equal 177 | */ 178 | function type_equality(a,b) { 179 | 180 | if (("Foo" == a.tag && "Foo" == b.tag) || 181 | ("Bar" == a.tag && "Bar" == b.tag) || 182 | ("Baz" == a.tag && "Baz" == b.tag)) { 183 | 184 | return true; 185 | 186 | } else if ("*" == a.tag && "*" == b.tag) { 187 | 188 | return type_equality(a.left, b.left) && type_equality(a.right, b.right); 189 | 190 | } else if ("->" == a.tag && "->" == b.tag) { 191 | 192 | return type_equality(a.arg, b.arg) && type_equality(a.ret, b.ret); 193 | 194 | } else { 195 | 196 | return false; 197 | 198 | } 199 | 200 | } 201 | 202 | /* 203 | * Checking if a variable has a type in a context 204 | */ 205 | function var_has_type(n,a,g) { 206 | if ("<>" == g.tag) { 207 | 208 | return false; 209 | 210 | } else if (",:" == g.tag) { 211 | 212 | if (n == g.name) { 213 | 214 | return type_equality(a, g.type); 215 | 216 | } else { 217 | 218 | return var_has_type(n, a, g.rest); 219 | 220 | } 221 | 222 | } 223 | } 224 | 225 | 226 | 227 | 228 | /* 229 | * G !- M : A 230 | * ========== 231 | */ 232 | function judgment_check(g, m, a) { 233 | 234 | /* 235 | * G !- M : A G !- N : B 236 | * ------------------------ * Intro 237 | * G !- (M,N) : A*B 238 | */ 239 | if ("(,)" == m.tag && "*" == a.tag) { 240 | 241 | return judgment_check(g, m.first, a.left) && 242 | judgment_check(g, m.second, a.right); 243 | 244 | } 245 | 246 | /* 247 | * G !- P : A*B G, x : A, y : B !- M : C 248 | * ----------------------------------------- * Elim 249 | * G !- split P as (x :: A, y :: B) in M : C 250 | */ 251 | else if ("split" == m.tag) { 252 | 253 | return judgment_check(g, m.pair, prod(m.type_a, m.type_b)) && 254 | judgment_check(snoc(snoc(g, m.name_x, m.type_a), m.name_y, m.type_b), m.body, a); 255 | 256 | } 257 | 258 | /* 259 | * G, x : A !- M : B 260 | * ------------------ -> Intro 261 | * G !- \x.M : A -> B 262 | */ 263 | else if ("lam" == m.tag && "->" == a.tag) { 264 | 265 | return judgment_check(snoc(g, m.name, a.arg), m.body, a.ret); 266 | 267 | } 268 | 269 | /* 270 | * G !- M : A -> B G !- N : A 271 | * ----------------------------- -> Elim 272 | * G !- app(M,N :: A) : B 273 | */ 274 | else if ("app" == m.tag) { 275 | 276 | return judgment_check(g, m.fun, arr(m.type_arg, a)) && 277 | judgment_check(g, m.arg, m.type_arg); 278 | 279 | } 280 | 281 | /* 282 | * x has type A in G 283 | * ----------------- variable 284 | * G !- x : A 285 | */ 286 | else if ("variable" == m.tag) { 287 | 288 | return var_has_type(m.name, a, g); 289 | 290 | } 291 | 292 | /* 293 | * Nothing else is well-typed 294 | */ 295 | else { 296 | 297 | return false; 298 | 299 | } 300 | 301 | } 302 | --------------------------------------------------------------------------------