feat(types): add type representation with unification and generalization
This commit is contained in:
1 parent
aba27152b8
commit
8c555e99e0
1 file changed
+166
@@ -0,0 +1,166 @@
|
|||||||
|
module Fml.Core.Types
|
||||||
|
|
||||||
|
// Types for the Hindley-Milner core. Type variables are mutable cells so
|
||||||
|
// unification can link them in place. Generalization uses the level trick
|
||||||
|
// (Remy / OCaml): a variable is generalizable when its level is deeper than
|
||||||
|
// the level of the let binding it escapes from.
|
||||||
|
|
||||||
|
type Ty =
|
||||||
|
| TInt
|
||||||
|
| TBool
|
||||||
|
| TString
|
||||||
|
| TUnit
|
||||||
|
| TArrow of Ty * Ty
|
||||||
|
| TTuple of Ty list
|
||||||
|
| TList of Ty
|
||||||
|
| TCon of string * Ty list
|
||||||
|
| TVar of TypeVar ref
|
||||||
|
|
||||||
|
and TypeVar =
|
||||||
|
| Unbound of int * int
|
||||||
|
| Link of Ty
|
||||||
|
| Generic of int
|
||||||
|
|
||||||
|
exception TypeError of string
|
||||||
|
|
||||||
|
let mutable private counter = 0
|
||||||
|
let mutable currentLevel = 0
|
||||||
|
|
||||||
|
let newVarId () =
|
||||||
|
counter <- counter + 1
|
||||||
|
counter
|
||||||
|
|
||||||
|
let newVar () = TVar(ref (Unbound(newVarId (), currentLevel)))
|
||||||
|
let enterLevel () = currentLevel <- currentLevel + 1
|
||||||
|
let leaveLevel () = currentLevel <- currentLevel - 1
|
||||||
|
|
||||||
|
let rec repr (t: Ty) : Ty =
|
||||||
|
match t with
|
||||||
|
| TVar r ->
|
||||||
|
match r.Value with
|
||||||
|
| Link inner ->
|
||||||
|
let resolved = repr inner
|
||||||
|
r.Value <- Link resolved
|
||||||
|
resolved
|
||||||
|
| _ -> t
|
||||||
|
| _ -> t
|
||||||
|
|
||||||
|
let prettyTy (t: Ty) : string =
|
||||||
|
let names = System.Collections.Generic.Dictionary<int, string>()
|
||||||
|
let mutable next = 0
|
||||||
|
let nameFor id =
|
||||||
|
match names.TryGetValue id with
|
||||||
|
| true, nm -> nm
|
||||||
|
| _ ->
|
||||||
|
let letter = char (int 'a' + next % 26)
|
||||||
|
let suffix = if next / 26 = 0 then "" else string (next / 26)
|
||||||
|
let nm = "'" + string letter + suffix
|
||||||
|
next <- next + 1
|
||||||
|
names.[id] <- nm
|
||||||
|
nm
|
||||||
|
let rec go prec (ty: Ty) =
|
||||||
|
match repr ty with
|
||||||
|
| TInt -> "int"
|
||||||
|
| TBool -> "bool"
|
||||||
|
| TString -> "string"
|
||||||
|
| TUnit -> "unit"
|
||||||
|
| TVar r ->
|
||||||
|
match r.Value with
|
||||||
|
| Unbound(id, _) -> nameFor id
|
||||||
|
| Generic id -> nameFor id
|
||||||
|
| Link inner -> go prec inner
|
||||||
|
| TList elem -> go 2 elem + " list"
|
||||||
|
| TCon(name, []) -> name
|
||||||
|
| TCon(name, [ single ]) -> go 2 single + " " + name
|
||||||
|
| TCon(name, args) ->
|
||||||
|
"(" + (args |> List.map (go 0) |> String.concat ", ") + ") " + name
|
||||||
|
| TTuple parts ->
|
||||||
|
let body = parts |> List.map (go 2) |> String.concat " * "
|
||||||
|
if prec > 1 then "(" + body + ")" else body
|
||||||
|
| TArrow(a, b) ->
|
||||||
|
let body = go 1 a + " -> " + go 0 b
|
||||||
|
if prec > 0 then "(" + body + ")" else body
|
||||||
|
go 0 t
|
||||||
|
|
||||||
|
let rec generalize (t: Ty) =
|
||||||
|
match repr t with
|
||||||
|
| TVar r ->
|
||||||
|
match r.Value with
|
||||||
|
| Unbound(id, level) when level > currentLevel -> r.Value <- Generic id
|
||||||
|
| _ -> ()
|
||||||
|
| TArrow(a, b) -> generalize a; generalize b
|
||||||
|
| TList elem -> generalize elem
|
||||||
|
| TCon(_, args) -> List.iter generalize args
|
||||||
|
| TTuple parts -> List.iter generalize parts
|
||||||
|
| TInt | TBool | TString | TUnit -> ()
|
||||||
|
|
||||||
|
let instantiate (t: Ty) : Ty =
|
||||||
|
let mapping = System.Collections.Generic.Dictionary<int, Ty>()
|
||||||
|
let rec go ty =
|
||||||
|
match repr ty with
|
||||||
|
| TVar r ->
|
||||||
|
match r.Value with
|
||||||
|
| Generic id ->
|
||||||
|
match mapping.TryGetValue id with
|
||||||
|
| true, fresh -> fresh
|
||||||
|
| _ ->
|
||||||
|
let fresh = newVar ()
|
||||||
|
mapping.[id] <- fresh
|
||||||
|
fresh
|
||||||
|
| _ -> ty
|
||||||
|
| TArrow(a, b) -> TArrow(go a, go b)
|
||||||
|
| TList elem -> TList(go elem)
|
||||||
|
| TCon(name, args) -> TCon(name, List.map go args)
|
||||||
|
| TTuple parts -> TTuple(List.map go parts)
|
||||||
|
| (TInt | TBool | TString | TUnit) as concrete -> concrete
|
||||||
|
go t
|
||||||
|
|
||||||
|
let private occursAndAdjust (id: int) (level: int) (ty: Ty) =
|
||||||
|
let rec go ty =
|
||||||
|
match repr ty with
|
||||||
|
| TVar r ->
|
||||||
|
match r.Value with
|
||||||
|
| Unbound(id2, lvl2) ->
|
||||||
|
if id2 = id then raise (TypeError "occurs check failed: infinite type")
|
||||||
|
elif lvl2 > level then r.Value <- Unbound(id2, level)
|
||||||
|
| Generic _ -> ()
|
||||||
|
| Link inner -> go inner
|
||||||
|
| TArrow(a, b) -> go a; go b
|
||||||
|
| TList elem -> go elem
|
||||||
|
| TCon(_, args) -> List.iter go args
|
||||||
|
| TTuple parts -> List.iter go parts
|
||||||
|
| TInt | TBool | TString | TUnit -> ()
|
||||||
|
go ty
|
||||||
|
|
||||||
|
let private failUnify a b =
|
||||||
|
raise (TypeError(sprintf "cannot unify %s with %s" (prettyTy a) (prettyTy b)))
|
||||||
|
|
||||||
|
let rec unify (t1: Ty) (t2: Ty) =
|
||||||
|
let a = repr t1
|
||||||
|
let b = repr t2
|
||||||
|
match a, b with
|
||||||
|
| TVar r1, TVar r2 when System.Object.ReferenceEquals(r1, r2) -> ()
|
||||||
|
| TVar r, other ->
|
||||||
|
match r.Value with
|
||||||
|
| Unbound(id, level) ->
|
||||||
|
occursAndAdjust id level other
|
||||||
|
r.Value <- Link other
|
||||||
|
| _ -> failUnify a b
|
||||||
|
| other, TVar r ->
|
||||||
|
match r.Value with
|
||||||
|
| Unbound(id, level) ->
|
||||||
|
occursAndAdjust id level other
|
||||||
|
r.Value <- Link other
|
||||||
|
| _ -> failUnify a b
|
||||||
|
| TInt, TInt | TBool, TBool | TString, TString | TUnit, TUnit -> ()
|
||||||
|
| TArrow(a1, a2), TArrow(b1, b2) ->
|
||||||
|
unify a1 b1
|
||||||
|
unify a2 b2
|
||||||
|
| TList e1, TList e2 -> unify e1 e2
|
||||||
|
| TCon(n1, a1), TCon(n2, a2) ->
|
||||||
|
if n1 <> n2 || List.length a1 <> List.length a2 then failUnify a b
|
||||||
|
else List.iter2 unify a1 a2
|
||||||
|
| TTuple xs, TTuple ys ->
|
||||||
|
if List.length xs <> List.length ys then failUnify a b
|
||||||
|
else List.iter2 unify xs ys
|
||||||
|
| _ -> failUnify a b
|
||||||
Reference in new issue
Block a user