;; lib/guest/hm.sx — Hindley-Milner type-inference foundations. ;; ;; Builds on lib/guest/match.sx (terms + unify) and ast.sx (canonical ;; AST shapes). This file ships the ALGEBRA — types, schemes, free ;; type-vars, generalize / instantiate, substitution composition — so a ;; full Algorithm W (or J) can be assembled on top either inside this ;; file or in a host-specific consumer (haskell/infer.sx, ;; lib/ocaml/types.sx, …). ;; ;; Per the brief the second consumer for this step is OCaml-on-SX ;; Phase 5 (paired sequencing). Until that lands, the algebra is the ;; deliverable; the host-flavoured assembly (lambda / app / let ;; inference rules with substitution threading) lives in the host. ;; ;; Types ;; ----- ;; A type is a canonical match.sx term — type variables use mk-var, ;; type constructors use mk-ctor: ;; (hm-tv NAME) type variable ;; (hm-arrow A B) A -> B ;; (hm-con NAME ARGS) named n-ary constructor ;; (hm-int) / (hm-bool) / (hm-string) primitive constructors ;; ;; Schemes ;; ------- ;; (hm-scheme VARS TYPE) ∀ VARS . TYPE ;; (hm-monotype TYPE) empty quantifier ;; (hm-scheme? S) (hm-scheme-vars S) (hm-scheme-type S) ;; ;; Free type variables ;; ------------------- ;; (hm-ftv TYPE) names occurring in TYPE ;; (hm-ftv-scheme S) free names (minus quantifiers) ;; (hm-ftv-env ENV) free across an env (name -> scheme) ;; ;; Substitution ;; ------------ ;; (hm-apply SUBST TYPE) substitute through a type ;; (hm-apply-scheme SUBST S) leaves bound vars alone ;; (hm-apply-env SUBST ENV) ;; (hm-compose S2 S1) apply S1 then S2 ;; ;; Generalize / Instantiate ;; ------------------------ ;; (hm-generalize TYPE ENV) → scheme over ftv(t) - ftv(env) ;; (hm-instantiate SCHEME COUNTER) → fresh-var instance ;; (hm-fresh-tv COUNTER) → (:var "tN"), bumps COUNTER ;; ;; Inference (literal only — the rest of Algorithm W lives in the host) ;; -------------------------------------------------------------------- ;; (hm-infer-literal EXPR) → {:subst {} :type T} ;; ;; A complete Algorithm W consumes this kit by assembling lambda / app ;; / let rules in the host language file. (define hm-tv (fn (name) (list :var name))) (define hm-con (fn (name args) (list :ctor name args))) (define hm-arrow (fn (a b) (hm-con "->" (list a b)))) (define hm-int (fn () (hm-con "Int" (list)))) (define hm-bool (fn () (hm-con "Bool" (list)))) (define hm-string (fn () (hm-con "String" (list)))) (define hm-scheme (fn (vars t) (list :scheme vars t))) (define hm-monotype (fn (t) (hm-scheme (list) t))) (define hm-scheme? (fn (s) (and (list? s) (not (empty? s)) (= (first s) :scheme)))) (define hm-scheme-vars (fn (s) (nth s 1))) (define hm-scheme-type (fn (s) (nth s 2))) (define hm-fresh-tv (fn (counter) (let ((n (first counter))) (begin (set-nth! counter 0 (+ n 1)) (hm-tv (str "t" (+ n 1))))))) (define hm-ftv-acc (fn (t acc) (cond ((is-var? t) (if (some (fn (n) (= n (var-name t))) acc) acc (cons (var-name t) acc))) ((is-ctor? t) (let ((a acc)) (begin (for-each (fn (x) (set! a (hm-ftv-acc x a))) (ctor-args t)) a))) (:else acc)))) (define hm-ftv (fn (t) (hm-ftv-acc t (list)))) (define hm-ftv-scheme (fn (s) (let ((qs (hm-scheme-vars s)) (all (hm-ftv (hm-scheme-type s)))) (filter (fn (n) (not (some (fn (q) (= q n)) qs))) all)))) (define hm-ftv-env (fn (env) (let ((acc (list))) (begin (for-each (fn (k) (for-each (fn (n) (when (not (some (fn (m) (= m n)) acc)) (set! acc (cons n acc)))) (hm-ftv-scheme (get env k)))) (keys env)) acc)))) (define hm-apply (fn (subst t) (walk* t subst))) (define hm-apply-scheme (fn (subst s) (let ((qs (hm-scheme-vars s)) (d {})) (begin (for-each (fn (k) (when (not (some (fn (q) (= q k)) qs)) (dict-set! d k (get subst k)))) (keys subst)) (hm-scheme qs (walk* (hm-scheme-type s) d)))))) (define hm-apply-env (fn (subst env) (let ((d {})) (begin (for-each (fn (k) (dict-set! d k (hm-apply-scheme subst (get env k)))) (keys env)) d)))) (define hm-compose (fn (s2 s1) (let ((d {})) (begin (for-each (fn (k) (dict-set! d k (walk* (get s1 k) s2))) (keys s1)) (for-each (fn (k) (when (not (has-key? d k)) (dict-set! d k (get s2 k)))) (keys s2)) d)))) (define hm-generalize (fn (t env) (let ((tvars (hm-ftv t)) (evars (hm-ftv-env env))) (let ((qs (filter (fn (n) (not (some (fn (m) (= m n)) evars))) tvars))) (hm-scheme qs t))))) (define hm-instantiate (fn (s counter) (let ((qs (hm-scheme-vars s)) (subst {})) (begin (for-each (fn (q) (set! subst (assoc subst q (hm-fresh-tv counter)))) qs) (walk* (hm-scheme-type s) subst))))) ;; Literal inference — the only AST kind whose typing rule is closed ;; in the kit. Lambda / app / let live in host code so the host's own ;; AST conventions stay untouched. (define hm-infer-literal (fn (expr) (let ((v (ast-literal-value expr))) (cond ((number? v) {:subst {} :type (hm-int)}) ((string? v) {:subst {} :type (hm-string)}) ((boolean? v) {:subst {} :type (hm-bool)}) (:else (error (str "hm-infer-literal: unknown kind: " v)))))))