├── README.md ├── env.js ├── terms.js ├── kinds.js ├── unification.js ├── index.js ├── util.js ├── kindInference.js ├── types.js └── inference.js /README.md: -------------------------------------------------------------------------------- 1 | Implementation of "Practical type inference for arbitrary-rank types" in Javascript. 2 | 3 | Added: 4 | - type applications 5 | - kind checking 6 | - higher kinded types 7 | - kind inference in type annotations 8 | 9 | Todo: 10 | - patterns 11 | - keep tvar names of annotated types 12 | - parser 13 | 14 | -------------------------------------------------------------------------------- /env.js: -------------------------------------------------------------------------------- 1 | const { tmetas, prune, tBool } = require('./types'); 2 | const { skolemCheck } = require('./util'); 3 | const { 4 | KFun, 5 | kType, 6 | } = require('./kinds'); 7 | 8 | const Env = (vars = {}, tcons = {}) => 9 | ({ vars, tcons }); 10 | 11 | const extendVar = (env, x, t) => { 12 | const n = Object.create(env.vars); 13 | n[x] = t; 14 | return Env(n, env.tcons); 15 | }; 16 | 17 | const skolemCheckEnv = (sk, env) => { 18 | const vars = env.vars; 19 | for (let k in vars) skolemCheck(sk, prune(vars[k])); 20 | }; 21 | 22 | const tmetasEnv = (env, free = [], tms = []) => { 23 | const vars = env.vars; 24 | for (let k in vars) tmetas(prune(vars[k]), free, tms); 25 | return tms; 26 | }; 27 | 28 | const initialEnv = Env({ 29 | True: tBool, 30 | False: tBool, 31 | }, { 32 | '->': KFun(kType, KFun(kType, kType)), 33 | Float: kType, 34 | Bool: kType, 35 | }); 36 | 37 | module.exports = { 38 | Env, 39 | extendVar, 40 | skolemCheckEnv, 41 | tmetasEnv, 42 | initialEnv, 43 | }; 44 | 45 | -------------------------------------------------------------------------------- /terms.js: -------------------------------------------------------------------------------- 1 | const { showTy } = require('./types'); 2 | 3 | const Var = name => ({ tag: 'Var', name }); 4 | const Lit = val => ({ tag: 'Lit', val }); 5 | const App = (left, right) => ({ tag: 'App', left, right }); 6 | const Abs = (name, body) => ({ tag: 'Abs', name, body }); 7 | const AbsT = (name, type, body) => ({ tag: 'AbsT', name, type, body }); 8 | const Let = (name, val, body) => ({ tag: 'Let', name, val, body }); 9 | const Ann = (term, type) => ({ tag: 'Ann', term, type }); 10 | const If = (cond, then_, else_) => ({ tag: 'If', cond, then_, else_ }); 11 | 12 | const showTerm = term => { 13 | if (term.tag === 'Var') return term.name; 14 | if (term.tag === 'Lit') return `${term.val}`; 15 | if (term.tag === 'Abs') return `(\\${term.name} -> ${showTerm(term.body)})`; 16 | if (term.tag === 'AbsT') 17 | return `(\\(${term.name} : ${showTy(term.type)}) -> ${showTerm(term.body)})`; 18 | if (term.tag === 'App') return `(${showTerm(term.left)} ${showTerm(term.right)})`; 19 | if (term.tag === 'Let') 20 | return `(let ${term.name} = ${showTerm(term.val)} in ${showTerm(term.body)})`; 21 | if (term.tag === 'Ann') return `(${showTerm(term.term)} : ${showTy(term.type)})`; 22 | if (term.tag === 'If') 23 | return `(if ${showTerm(term.cond)} then ${showTerm(term.then_)} else ${showTerm(term.else_)})`; 24 | }; 25 | 26 | module.exports = { 27 | Var, 28 | Lit, 29 | App, 30 | Abs, 31 | AbsT, 32 | Let, 33 | Ann, 34 | If, 35 | showTerm, 36 | }; 37 | 38 | -------------------------------------------------------------------------------- /kinds.js: -------------------------------------------------------------------------------- 1 | const KCon = name => ({ tag: 'KCon', name }); 2 | const KFun = (left, right) => ({ tag: 'KFun', left, right }); 3 | const KMeta = (id, kind = null) => ({ tag: 'KMeta', id, kind }); 4 | 5 | const kType = KCon('Type'); 6 | 7 | const showKind = ki => { 8 | if (ki.tag === 'KCon') return ki.name; 9 | if (ki.tag === 'KMeta') return `?${ki.id}`; 10 | if (ki.tag === 'KFun') return `(${showKind(ki.left)} -> ${showKind(ki.right)})`; 11 | }; 12 | 13 | const pruneKind = ki => { 14 | if (ki.tag === 'TMeta') { 15 | if (!ki.kind) return ki; 16 | const k = pruneKind(ki.kind); 17 | ki.kind = k; 18 | return k; 19 | } 20 | if (ki.tag === 'KFun') { 21 | const l = pruneKind(ki.left); 22 | const r = pruneKind(ki.right); 23 | return l === ki.left && r === ki.right ? ki : KFun(l, r); 24 | } 25 | return ki; 26 | }; 27 | 28 | const occursKMeta = (x, k) => { 29 | if (x === k) return true; 30 | if (k.tag === 'KFun') return occursKMeta(x, k.left) || occursKMeta(x, k.right); 31 | return false; 32 | }; 33 | 34 | const eqKind = (a, b) => { 35 | if (a === b) return true; 36 | if (a.tag === 'KCon') 37 | return b.tag === 'KCon' && a.name === b.name; 38 | if (a.tag === 'KFun') 39 | return b.tag === 'KFun' && eqKind(a.left, b.left) && eqKind(a.right, b.right); 40 | return false; 41 | }; 42 | 43 | module.exports = { 44 | KCon, 45 | KFun, 46 | KMeta, 47 | kType, 48 | showKind, 49 | pruneKind, 50 | occursKMeta, 51 | eqKind, 52 | }; 53 | -------------------------------------------------------------------------------- /unification.js: -------------------------------------------------------------------------------- 1 | const { 2 | isTFun, 3 | TFun, 4 | showTy, 5 | occursTMeta, 6 | } = require('./types'); 7 | const { 8 | kType, 9 | eqKind, 10 | showKind, 11 | } = require('./kinds'); 12 | const { 13 | terr, 14 | freshTMeta, 15 | } = require('./util'); 16 | const { 17 | kindOf, 18 | } = require('./kindInference'); 19 | 20 | const bindTMeta = (env, x, t) => { 21 | if (x.type) return unify(env, x.type, t); 22 | if (t.tag === 'TMeta' && t.type) return unify(env, x, t.type); 23 | if (occursTMeta(x, t)) return terr(`${showTy(x)} occurs in ${showTy(t)}`); 24 | const k1 = kindOf(env, x); 25 | const k2 = kindOf(env, t); 26 | if (!eqKind(k1, k2)) 27 | return terr(`kind mismatch in unification of ${showTy(x)} ~ ${showTy(t)}: ${showKind(k1)} ~ ${showKind(k2)}`); 28 | x.type = t; 29 | }; 30 | const unify = (env, a, b) => { 31 | if (a.tag === 'TVar' || b.tag === 'TVar') 32 | return terr(`tvar in unify: ${showTy(a)} ~ ${showTy(b)}`); 33 | if (a === b) return; 34 | if (a.tag === 'TMeta') return bindTMeta(env, a, b); 35 | if (b.tag === 'TMeta') return bindTMeta(env, b, a); 36 | if (a.tag === 'TApp' && b.tag === 'TApp') { 37 | unify(env, a.left, b.left); 38 | return unify(env, a.right, b.right); 39 | } 40 | if (a.tag === 'TSkol' && b.tag === 'TSkol' && a.id === b.id) return; 41 | if (a.tag === 'TCon' && b.tag === 'TCon' && a.name === b.name) return; 42 | return terr(`failed to unify: ${showTy(a)} ~ ${showTy(b)}`); 43 | }; 44 | 45 | const unifyTFun = (env, ty) => { 46 | if (isTFun(ty)) return ty; 47 | const fn = TFun(freshTMeta(kType), freshTMeta(kType)); 48 | unify(env, ty, fn); 49 | return fn; 50 | }; 51 | 52 | module.exports = { 53 | unify, 54 | unifyTFun, 55 | }; 56 | 57 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const { 2 | Var, 3 | App, 4 | Abs, 5 | AbsT, 6 | Let, 7 | Ann, 8 | If, 9 | Lit, 10 | showTerm, 11 | } = require('./terms'); 12 | const { 13 | TCon, 14 | TApp, 15 | TFun, 16 | TVar, 17 | TForall, 18 | showTy, 19 | tBool, 20 | } = require('./types'); 21 | const { kType, KFun } = require('./kinds'); 22 | const { infer } = require('./inference'); 23 | const { Env, initialEnv } = require('./env'); 24 | 25 | function kfun() { return Array.prototype.slice.call(arguments).reduceRight((x, y) => KFun(y, x)) } 26 | 27 | const tv = TVar; 28 | function tfun() { return Array.prototype.slice.call(arguments).reduceRight((x, y) => TFun(y, x)) } 29 | function tapp() { return Array.prototype.slice.call(arguments).reduce(TApp) } 30 | const tforall = (tvs, ty) => TForall(tvs, null, ty); 31 | 32 | const v = Var; 33 | function app() { return Array.prototype.slice.call(arguments).reduce(App) } 34 | const abs = (ns, body) => ns.reduceRight((x, y) => Array.isArray(y) ? AbsT(y[0], y[1], x) : Abs(y, x), body); 35 | 36 | const tid = TForall(['t'], [kType], tfun(tv('t'), tv('t'))); 37 | const ListC = TCon('List'); 38 | const List = t => tapp(ListC, t); 39 | 40 | const env = initialEnv; 41 | env.vars.id = tid; 42 | env.vars.singleton = TForall(['t'], [kType], TFun(tv('t'), List(tv('t')))); 43 | env.vars.refl = TForall(['f', 'a', 'b'], [KFun(kType, kType), kType, kType], TFun(tapp(tv('f'), tv('a')), tapp(tv('f'), tv('b')))); 44 | env.tcons.List = kfun(kType, kType); 45 | 46 | const t1 = Ann(abs(['x', 'y', 'z'], v('z')), tfun(tBool, TForall(['a'], [], tfun(tBool, tv('a'), tv('a'))))); 47 | const t2 = Ann(abs(['x', 'y', 'z'], v('z')), tfun(tBool, tBool, TForall(['a'], [], tfun(tv('a'), tv('a'))))); 48 | const term = If(v('True'), t1, t2); 49 | try { 50 | console.log(showTerm(term)); 51 | time = Date.now(); 52 | const ty = infer(env, term); 53 | time = Date.now() - time; 54 | console.log(`${time}`); 55 | console.log(showTy(ty)); 56 | } catch (err) { 57 | console.log(`${err}`); 58 | console.log(err); 59 | } 60 | 61 | -------------------------------------------------------------------------------- /util.js: -------------------------------------------------------------------------------- 1 | const { 2 | TFun, 3 | TMeta, 4 | TSkol, 5 | showTy, 6 | prune, 7 | tmetas, 8 | substTVar, 9 | isTFun, 10 | } = require('./types'); 11 | const { KMeta, kType } = require('./kinds'); 12 | 13 | const terr = msg => { throw new TypeError(msg) }; 14 | 15 | let _id = 0; 16 | const resetId = () => { _id = 0 }; 17 | const freshId = () => _id++; 18 | 19 | const freshTMeta = kind => TMeta(freshId(), kind); 20 | const freshTSkol = (name, kind) => TSkol(name, freshId(), kind); 21 | const freshKMeta = () => KMeta(freshId()); 22 | 23 | const Check = type => ({ tag: 'Check', type }); 24 | const Infer = () => ({ tag: 'Infer', type: null }); 25 | const showEx = ex => { 26 | if (ex.tag === 'Check') return `Check(${showTy(ex.type)})`; 27 | if (ex.tag === 'Infer') return `Infer(${ex.type ? showTy(ex.type) : '...'})`; 28 | }; 29 | 30 | const instantiate = ty => { 31 | if (ty.tag !== 'TForall') return ty; 32 | const m = {}; 33 | for (let i = 0, l = ty.tvs.length; i < l; i++) 34 | m[ty.tvs[i]] = freshTMeta(ty.ks[i]); 35 | return substTVar(m, ty.type); 36 | }; 37 | 38 | const skolemise = (ty, sk = []) => { 39 | if (ty.tag === 'TForall') { 40 | const m = {}; 41 | for (let i = 0, l = ty.tvs.length; i < l; i++) { 42 | const k = freshTSkol(ty.tvs[i], ty.ks[i]); 43 | m[ty.tvs[i]] = k; 44 | sk.push(k); 45 | } 46 | return skolemise(substTVar(m, ty.type), sk); 47 | } 48 | if (isTFun(ty)) { 49 | const { left: { right: left }, right } = ty; 50 | const b = skolemise(right, sk); 51 | return b === right ? ty : TFun(left, b); 52 | } 53 | return ty; 54 | }; 55 | 56 | const skolemCheck = (sk, ty) => { 57 | if (ty.tag === 'TSkol' && sk.indexOf(ty) >= 0) 58 | return terr(`skolem check failed: ${showTy(ty)}`); 59 | if (ty.tag === 'TApp') { 60 | skolemCheck(sk, ty.left); 61 | return skolemCheck(sk, ty.right); 62 | } 63 | if (ty.tag === 'TForall') 64 | return skolemCheck(sk, ty.type); 65 | }; 66 | 67 | module.exports = { 68 | terr, 69 | resetId, 70 | freshId, 71 | freshTMeta, 72 | freshTSkol, 73 | freshKMeta, 74 | 75 | Check, 76 | Infer, 77 | showEx, 78 | 79 | instantiate, 80 | skolemise, 81 | 82 | skolemCheck, 83 | }; 84 | 85 | -------------------------------------------------------------------------------- /kindInference.js: -------------------------------------------------------------------------------- 1 | const { 2 | TApp, 3 | TForall, 4 | substTVar, 5 | TVar, 6 | } = require('./types'); 7 | const { 8 | showKind, 9 | pruneKind, 10 | occursKMeta, 11 | KFun, 12 | kType, 13 | } = require('./kinds'); 14 | const { 15 | terr, 16 | freshKMeta, 17 | freshTSkol, 18 | } = require('./util'); 19 | 20 | const bindKMeta = (x, t) => { 21 | if (x.kind) return unifyKind(x.kind, t); 22 | if (t.tag === 'KMeta' && t.kind) return unifyKind(x, t.kind); 23 | if (occursKMeta(x, t)) return terr(`${showTy(x)} occurs in ${showTy(t)}`); 24 | x.kind = t; 25 | }; 26 | const unifyKind = (a, b) => { 27 | if (a === b) return; 28 | if (a.tag === 'KMeta') return bindKMeta(a, b); 29 | if (b.tag === 'KMeta') return bindKMeta(b, a); 30 | if (a.tag === 'KFun' && b.tag === 'KFun') { 31 | unifyKind(a.left, b.left); 32 | return unifyKind(a.right, b.right); 33 | } 34 | if (a.tag === 'KCon' && b.tag === 'KCon' && a.name === b.name) return; 35 | return terr(`failed to unify kinds: ${showKind(a)} ~ ${showKind(b)}`); 36 | }; 37 | 38 | const inferKindR = (env, t) => { 39 | if (t.tag === 'TMeta') return [t.kind, t]; 40 | if (t.tag === 'TVar') return terr(`tvar ${t.name} in inferKindR`); 41 | if (t.tag === 'TSkol') return [t.kind, t]; 42 | if (t.tag === 'TCon') { 43 | if (!env.tcons[t.name]) 44 | return terr(`undefined type constructor ${showTy(t)}`); 45 | return [env.tcons[t.name], t]; 46 | } 47 | if (t.tag === 'TApp') { 48 | const [l, tl] = inferKindR(env, t.left); 49 | const [r, tr] = inferKindR(env, t.right); 50 | const km = freshKMeta(); 51 | unifyKind(l, KFun(r, km)); 52 | return [km, tl === t.left && tr === t.right ? t : TApp(tl, tr)]; 53 | } 54 | if (t.tag === 'TForall') { 55 | const { tvs, type } = t; 56 | const ks = t.ks || []; 57 | const m = {}; 58 | const nks = Array(tvs.length); 59 | for (let i = 0, l = tvs.length; i < l; i++) { 60 | const ki = ks[i] || freshKMeta(); 61 | const k = freshTSkol(tvs[i], ki); 62 | m[tvs[i]] = k; 63 | nks[i] = ki; 64 | } 65 | const [km, b] = inferKindR(env, substTVar(m, type)); 66 | return [km, TForall(tvs, nks, b)]; 67 | } 68 | }; 69 | 70 | const defaultKindInKind = k => { 71 | if (k.tag === 'KCon') return k; 72 | if (k.tag === 'KMeta') { 73 | if (k.kind) return defaultKindInKind(k.kind); 74 | return kType; 75 | } 76 | if (k.tag === 'KFun') { 77 | const l = defaultKindInKind(k.left); 78 | const r = defaultKindInKind(k.right); 79 | return l === k.left && r === k.right ? k : KFun(l, r); 80 | } 81 | }; 82 | 83 | const defaultKind = t => { 84 | if (t.tag === 'TApp') { 85 | const l = defaultKind(t.left); 86 | const r = defaultKind(t.right); 87 | return l === t.left && r === t.right ? t : TApp(l, r); 88 | } 89 | if (t.tag === 'TForall') { 90 | const { tvs, ks, type } = t; 91 | const nks = ks.map(k => k ? defaultKindInKind(k) : kType); 92 | const b = defaultKind(type); 93 | return TForall(tvs, nks, b); 94 | } 95 | if (t.tag === 'TSkol') 96 | return TVar(t.name); 97 | if (t.tag === 'TMeta') 98 | return terr(`tmeta ?${t.id} in defaultKind`); 99 | return t; 100 | }; 101 | 102 | const inferKind = (env, ty) => { 103 | const [_, ti] = inferKindR(env, ty); 104 | return defaultKind(ti); 105 | }; 106 | 107 | const kindOf = (env, t) => { 108 | if (t.tag === 'TMeta') return t.kind; 109 | if (t.tag === 'TSkol') return t.kind; 110 | if (t.tag === 'TCon') 111 | return env.tcons[t.name] || terr(`undefined type constructor ${showTy(t)}`); 112 | if (t.tag === 'TApp') { 113 | const f = kindOf(env, t.left); 114 | if (f.tag !== 'KFun') 115 | return terr(`not a kind fun in left side of type application (${showTy(t)}): ${showKind(f)}`); 116 | return f.right; 117 | } 118 | if (t.tag === 'TForall') return terr(`tforall ${showTy(t)} in kindOf`); 119 | if (t.tag === 'TVar') return terr(`tvar ${showTy(t)} in kindOf`); 120 | }; 121 | 122 | module.exports = { 123 | inferKind, 124 | kindOf, 125 | }; 126 | 127 | -------------------------------------------------------------------------------- /types.js: -------------------------------------------------------------------------------- 1 | const { showKind } = require('./kinds'); 2 | 3 | const TCon = name => ({ tag: 'TCon', name }); 4 | const TVar = name => ({ tag: 'TVar', name }); 5 | const TMeta = (id, kind, type = null) => ({ tag: 'TMeta', id, kind, type }); 6 | const TSkol = (name, id, kind) => ({ tag: 'TSkol', name, id, kind }); 7 | const TApp = (left, right) => ({ tag: 'TApp', left, right }); 8 | const TForall = (tvs, ks, type) => ({ tag: 'TForall', tvs, ks, type }); 9 | 10 | const tFun = TCon('->'); 11 | const TFun = (left, right) => TApp(TApp(tFun, left), right); 12 | const isTFun = ty => 13 | ty.tag === 'TApp' && ty.left.tag === 'TApp' && 14 | (ty.left.left === tFun || (ty.left.left.tag === 'TCon' && ty.left.left.name === tFun.name)); 15 | 16 | const tFloat = TCon('Float'); 17 | const tBool = TCon('Bool'); 18 | 19 | const showTy = ty => { 20 | if (ty.tag === 'TCon') return ty.name; 21 | if (ty.tag === 'TVar') return ty.name; 22 | if (ty.tag === 'TMeta') return `?${ty.id}`; 23 | if (ty.tag === 'TSkol') return `${ty.name}\$${ty.id}`; 24 | if (ty.tag === 'TForall') 25 | return `(forall ${ty.tvs.map((tv, i) => 26 | ty.ks && ty.ks[i] ? `(${tv} : ${showKind(ty.ks[i])})` : `${tv}`).join('')}. ${showTy(ty.type)})`; 27 | if (isTFun(ty)) return `(${showTy(ty.left.right)} -> ${showTy(ty.right)})`; 28 | if (ty.tag === 'TApp') return `(${showTy(ty.left)} ${showTy(ty.right)})`; 29 | }; 30 | 31 | const substTVar = (map, ty) => { 32 | if (ty.tag === 'TVar') return map[ty.name] || ty; 33 | if (ty.tag === 'TApp') { 34 | const { left, right } = ty; 35 | const a = substTVar(map, left); 36 | const b = substTVar(map, right); 37 | return left === a && right === b ? ty : TApp(a, b); 38 | } 39 | if (ty.tag === 'TForall') { 40 | const { tvs, ks, type } = ty; 41 | const m = {}; 42 | for (let k in map) if (tvs.indexOf(k) < 0) m[k] = map[k]; 43 | const b = substTVar(m, type); 44 | return b === type ? ty : TForall(tvs, ks, b); 45 | } 46 | return ty; 47 | }; 48 | 49 | const tmetas = (ty, free = [], tms = []) => { 50 | if (ty.tag === 'TMeta') { 51 | if (free.indexOf(ty) >= 0 || tms.indexOf(ty) >= 0) return tms; 52 | tms.push(ty); 53 | return tms; 54 | } 55 | if (ty.tag === 'TApp') 56 | return tmetas(ty.right, free, tmetas(ty.left, free, tms)); 57 | if (ty.tag === 'TForall') 58 | return tmetas(ty.type, free, tms); 59 | return tms; 60 | }; 61 | 62 | const tbinders = (ty, bs = []) => { 63 | if (ty.tag === 'TApp') return tbinders(ty.right, tbinders(ty.left, bs)); 64 | if (ty.tag === 'TForall') { 65 | for (let i = 0, l = ty.tvs.length; i < l; i++) { 66 | const x = ty.tvs[i]; 67 | if (bs.indexOf(x) < 0) bs.push(x); 68 | } 69 | return tbinders(ty.type, bs); 70 | } 71 | return bs; 72 | }; 73 | 74 | const prune = ty => { 75 | if (ty.tag === 'TMeta') { 76 | if (!ty.type) return ty; 77 | const t = prune(ty.type); 78 | ty.type = t; 79 | return t; 80 | } 81 | if (ty.tag === 'TApp') { 82 | const { left, right } = ty; 83 | const a = prune(left); 84 | const b = prune(right); 85 | return left === a && right === b ? ty : TApp(a, b); 86 | } 87 | if (ty.tag === 'TForall') { 88 | const { tvs, ks, type } = ty; 89 | const b = prune(type); 90 | return b === type ? ty : TForall(tvs, ks, b); 91 | } 92 | return ty; 93 | }; 94 | 95 | const occursTMeta = (x, t) => { 96 | if (x === t) return true; 97 | if (t.tag === 'TApp') return occursTMeta(x, t.left) || occursTMeta(x, t.right); 98 | if (t.tag === 'TForall') return occursTMeta(x, t.type); 99 | return false; 100 | }; 101 | 102 | const quantify = (tms, ty) => { 103 | const len = tms.length; 104 | if (len === 0) return ty; 105 | const used = tbinders(ty); 106 | const tvs = Array(len); 107 | const ks = Array(len); 108 | let i = 0; 109 | let l = 0; 110 | let j = 0; 111 | while (i < len) { 112 | const v = `${String.fromCharCode(l + 97)}${j > 0 ? j : ''}`; 113 | if (used.indexOf(v) < 0) { 114 | tms[i].type = TVar(v); 115 | tvs[i] = v; 116 | ks[i] = tms[i].kind; 117 | i++; 118 | } 119 | l = (l + 1) % 26; 120 | if (l === 0) j++; 121 | } 122 | return TForall(tvs, ks, prune(ty)); 123 | }; 124 | 125 | module.exports = { 126 | TCon, 127 | TVar, 128 | TMeta, 129 | TSkol, 130 | TApp, 131 | TFun, 132 | TForall, 133 | showTy, 134 | 135 | tFun, 136 | TFun, 137 | isTFun, 138 | tFloat, 139 | tBool, 140 | 141 | substTVar, 142 | tmetas, 143 | tbinders, 144 | prune, 145 | occursTMeta, 146 | quantify, 147 | }; 148 | 149 | -------------------------------------------------------------------------------- /inference.js: -------------------------------------------------------------------------------- 1 | const { 2 | TFun, 3 | tmetas, 4 | prune, 5 | quantify, 6 | isTFun, 7 | tFloat, 8 | tBool, 9 | } = require('./types'); 10 | const { kType } = require('./kinds'); 11 | const { 12 | terr, 13 | resetId, 14 | freshTMeta, 15 | Check, 16 | Infer, 17 | instantiate, 18 | skolemise, 19 | skolemCheck, 20 | } = require('./util'); 21 | const { 22 | unify, 23 | unifyTFun, 24 | } = require('./unification'); 25 | const { 26 | skolemCheckEnv, 27 | tmetasEnv, 28 | extendVar, 29 | } = require('./env'); 30 | const { inferKind } = require('./kindInference'); 31 | 32 | const checkRho = (env, term, ty) => { 33 | // console.log(`checkRho ${showTerm(term)} : ${showTy(ty)}`); 34 | return tcRho(env, term, Check(ty)); 35 | }; 36 | const inferRho = (env, term) => { 37 | // console.log(`inferRho ${showTerm(term)}`); 38 | const i = Infer(); 39 | tcRho(env, term, i); 40 | if (!i.type) return terr(`inferRho failed`); 41 | return i.type; 42 | }; 43 | 44 | const tcRho = (env, term, ex) => { 45 | // console.log(`tcRho ${showTerm(term)} : ${showEx(ex)}`); 46 | if (term.tag === 'Var') { 47 | const ty = env.vars[term.name]; 48 | if (!ty) return terr(`undefined var: ${term.name}`); 49 | return instSigma(env, ty, ex); 50 | } 51 | if (term.tag === 'Lit') 52 | return instSigma(env, tFloat, ex); 53 | if (term.tag === 'App') { 54 | const ty = inferRho(env, term.left); 55 | const { left: { right: left }, right } = unifyTFun(env, ty); 56 | checkSigma(env, term.right, left); 57 | return instSigma(env, right, ex); 58 | } 59 | if (term.tag === 'Abs') { 60 | if (ex.tag === 'Check') { 61 | const { left: { right: left }, right } = unifyTFun(env, ex.type); 62 | const nenv = extendVar(env, term.name, left); 63 | return checkRho(nenv, term.body, right); 64 | } else if (ex.tag === 'Infer') { 65 | const ty = freshTMeta(kType); 66 | const nenv = extendVar(env, term.name, ty); 67 | const bty = inferRho(nenv, term.body); 68 | return ex.type = TFun(ty, bty); 69 | } 70 | } 71 | if (term.tag === 'AbsT') { 72 | const type = inferKind(env, term.type); 73 | if (ex.tag === 'Check') { 74 | const { left: { right: left }, right } = unifyTFun(env, ex.type); 75 | subsCheck(env, left, type); 76 | const nenv = extendVar(env, term.name, type); 77 | return checkRho(nenv, term.body, right); 78 | } else if (ex.tag === 'Infer') { 79 | const nenv = extendVar(env, term.name, type); 80 | const bty = inferRho(nenv, term.body); 81 | return ex.type = TFun(type, bty); 82 | } 83 | } 84 | if (term.tag === 'If') { 85 | if (ex.tag === 'Check') { 86 | checkRho(env, term.cond, tBool); 87 | tcRho(env, term.then_, ex); 88 | tcRho(env, term.else_, ex); 89 | return; 90 | } else if (ex.tag === 'Infer') { 91 | checkRho(env, term.cond, tBool); 92 | const ty1 = inferRho(env, term.then_); 93 | const ty2 = inferRho(env, term.else_); 94 | subsCheck(env, ty1, ty2); 95 | subsCheck(env, ty2, ty1); 96 | return ex.type = ty1; 97 | } 98 | } 99 | if (term.tag === 'Let') { 100 | const ty = inferSigma(env, term.val); 101 | const nenv = extendVar(env, term.name, ty); 102 | return tcRho(nenv, term.body, ex); 103 | } 104 | if (term.tag === 'Ann') { 105 | const type = inferKind(env, term.type); 106 | checkSigma(env, term.term, type); 107 | return instSigma(env, type, ex); 108 | } 109 | }; 110 | 111 | const inferSigma = (env, term) => { 112 | // console.log(`inferSigma ${showTerm(term)}`); 113 | const ty = inferRho(env, term); 114 | const etms = tmetasEnv(env); 115 | const tms = tmetas(prune(ty), etms); 116 | return quantify(tms, ty); 117 | }; 118 | 119 | const checkSigma = (env, term, ty) => { 120 | // console.log(`checkSigma ${showTerm(term)} : ${showTy(ty)}`); 121 | const sk = []; 122 | const rho = skolemise(ty, sk); 123 | checkRho(env, term, rho); 124 | skolemCheck(sk, prune(ty)); 125 | skolemCheckEnv(sk, env); 126 | }; 127 | 128 | const subsCheck = (env, a, b) => { 129 | // console.log(`subsCheck ${showTy(a)} <: ${showTy(b)}`); 130 | const sk = []; 131 | const rho = skolemise(b, sk); 132 | subsCheckRho(env, a, rho); 133 | skolemCheck(sk, prune(a)); 134 | skolemCheck(sk, prune(b)); 135 | }; 136 | const subsCheckRho = (env, a, b) => { 137 | // console.log(`subsCheckRho ${showTy(a)} <: ${showTy(b)}`); 138 | if (a.tag === 'TForall') 139 | return subsCheckRho(env, instantiate(a), b); 140 | if (isTFun(b)) 141 | return subsCheckTFun(env, unifyTFun(env, a), b); 142 | if (isTFun(a)) 143 | return subsCheckTFun(env, a, unifyTFun(env, b)); 144 | return unify(env, a, b); 145 | }; 146 | const subsCheckTFun = (env, a, b) => { 147 | // console.log(`subsCheckTFun ${showTy(a)} <: ${showTy(b)}`); 148 | subsCheck(env, b.left.right, a.left.right); 149 | return subsCheck(env, a.right, b.right); 150 | }; 151 | 152 | const instSigma = (env, ty, ex) => { 153 | // console.log(`instSigma ${showTy(ty)} @ ${showEx(ex)}`); 154 | if (ex.tag === 'Check') 155 | return subsCheckRho(env, ty, ex.type); 156 | return ex.type = instantiate(ty); 157 | }; 158 | 159 | const infer = (env, term) => { 160 | resetId(); 161 | return prune(inferSigma(env, term)); 162 | }; 163 | 164 | module.exports = { 165 | infer, 166 | }; 167 | --------------------------------------------------------------------------------