type typ = | Tint | Tarrow of typ * typ | Tproduct of typ * typ | Tvar of tvar and tvar = { id : int; mutable def : typ option }O tipo typ dos tipos de mini-ML é mutualmente recursivo com o tipo tvar das variáveis de tipo. De facto, uma variável de tipo contém uma eventual definição (obtida aquando de uma unificação), que é um tipo. Assim { id = 1; def = None } "ainda" é uma variável de tipo, mas { id = 2; def = Some Tint } é uma antiga variável de tipo que foi definida como sendo igual ao tipo Tint e que deve ser doravante tratada como se fosse o tipo Tint.
Introduzimos em seguida um módulo V que encapsula o tipo tvar com uma função de comparação e uma função que cria uma nova variável de tipo.
module V = struct type t = tvar let compare v1 v2 = Pervasives.compare v1.id v2.id let equal v1 v2 = v1.id = v2.id let create = let r = ref 0 in fun () -> incr r; { id = !r; def = None } end
Escrever uma função
val head : typ -> typque normaliza um tipo em cabeça, i.e., head t retorna um tipo igual a t que não é da forma Tvar { def = Some _}. Dito de outra forma, head t segue as definições de variável de tipo em cabeça t, enquanto os houver.
Escrever uma função
val canon : typ -> typque normaliza um tipo integralmente, i.e., que aplica a função head acima definida, em profundidade. Esta segunda função será utilizada somente para a visualização dos tipos (no resultados de uma operação de tipagem ou numa mensagem de erro).
Poderemos testar com
let () = let a = V.create () in let b = V.create () in let ta = Tvar a in let tb = Tvar b in assert (head ta == ta); assert (head tb == tb); let ty = Tarrow (ta, tb) in a.def <- Some tb; assert (head ta == tb); assert (head tb == tb); b.def <- Some Tint; assert (head ta = Tint); assert (head tb = Tint); assert (canon ta = Tint); assert (canon tb = Tint); assert (canon ty = Tarrow (Tint, Tint))
exception UnificationFailure of typ * typ let unification_error t1 t2 = raise (UnificationFailure (canon t1, canon t2))Escrever uma função
val occur : tvar -> typ -> boolque testa a ocorrência de uma variável de tipo num tipo (occur-check). Poderemos supor que a variável não está definida. No entanto, o tipo pode conter variáveis definidas e convém aplicar-lhe a função head antes de a examinar.
Escrever uma função
val unify : typ -> typ -> unitrealizando a unificação de dois tipos. Aqui também convém utilizar a função head sobre os tipos passados em argumento antes de os examinar.
Poderemos testar com
let () = let a = V.create () in let b = V.create () in let ta = Tvar a in let tb = Tvar b in assert (occur a ta); assert (occur b tb); assert (not (occur a tb)); let ty = Tarrow (ta, tb) in assert (occur a ty); assert (occur b ty); (* unifica 'a-> 'b e int->int *) unify ty (Tarrow (Tint, Tint)); assert (canon ta = Tint); assert (canon ty = Tarrow (Tint, Tint)); (* unifica 'c e int->int *) let c = V.create () in let tc = Tvar c in unify tc ty; assert (canon tc = Tarrow (Tint, Tint)) let cant_unify ty1 ty2 = try let _ = unify ty1 ty2 in false with UnificationFailure _ -> true let () = assert (cant_unify Tint (Tarrow (Tint, Tint))); assert (cant_unify Tint (Tproduct (Tint, Tint))); let a = V.create () in let ta = Tvar a in unify ta (Tarrow (Tint, Tint)); assert (cant_unify ta Tint)
module Vset = Set.Make(V)Escrever uma função
val fvars : typ -> Vset.tque calcula um conjunto das variáveis livres de um tipo. Uma vez ainda, convém utilizar a função head com o devido proveito para só considerar as variáveis que ainda não estão definidas.
Poderemos testar com
let () = assert (Vset.is_empty (fvars (Tarrow (Tint, Tint)))); let a = V.create () in let ta = Tvar a in let ty = Tarrow (ta, ta) in assert (Vset.equal (fvars ty) (Vset.singleton a)); unify ty (Tarrow (Tint, Tint)); assert (Vset.is_empty (fvars ty))
type schema = { vars : Vset.t; typ : typ }Introduzimos então dicionários utilizando cadeias de caracteres como chaves
module Smap = Map.Make(String)e o tipo OCaml seguinte para os ambientes de tipagem :
type env = { bindings : schema Smap.t; fvars : Vset.t }O primeiro campo, bindings, contém os elementos do ambiente de tipagem. O segundo, fvars, contém o conjunto de todas as variáveis livres nas entradas de bindings. O campo fvars só está aqui para evitar recalcular de cada vez o conjunto das variáveis livres do ambiente aquando da operação de generalização. Nota : certas destas variáveis podem ficar definidas entre o momento onde se encontram neste conjunto e o momento onde este utilizado. Será conveniente actualizar este conjunto antes de se servir dele (por exemplo aplicando fvars (Tvar v) a cada variável v e calculando a união dos resultados).
Definir o ambiente de tipagem vazio :
val empty : envescrever a função
val add : string -> typ -> env -> envque junta um elemento dentro do ambiente de tipagem sem generalização (este será utilizado para a tipagem de uma função). Não esqueceremos a actualização do campo fvars do ambiente.
Escrever a função
val add_gen : string -> typ -> env -> envque junta um elemento no ambiente de tipagem generalizando o seu tipo relativamente a todas as variáveis de tipo livres não aparecendo no ambiente (esta será utilizada na tipagem de um let).
Escrever a função
val find : string -> env -> typque retorna o tipo associado a um identificador num ambiente, depois de ter substituido todas as variáveis do esquema correspondente por variáveis de tipos frescas. Cuidado : pode haver várias ocorrências da mesma variável no tipo e convém, claro, substitui-las por uma mesma variável fresca.
type expression = | Var of string | Const of int | Op of string | Fun of string * expression | App of expression * expression | Pair of expression * expression | Let of string * expression * expressionEscrever a função
val w : env -> expression -> typque implementa o algoritmo W.
Para testar, utilizaremos a função seguinte
let typeof e = canon (w empty e)Alguns testes positivos (a expressão e o tipo esperado estão indicados nos comentários) :
(* 1 : int *) let () = assert (typeof (Const 1) = Tint) (* fun x -> x : 'a -> 'a *) let () = assert (match typeof (Fun ("x", Var "x")) with | Tarrow (Tvar v1, Tvar v2) -> V.equal v1 v2 | _ -> false) (* fun x -> x+1 : int -> int *) let () = assert (typeof (Fun ("x", App (Op "+", Pair (Var "x", Const 1)))) = Tarrow (Tint, Tint)) (* fun x -> x+x : int -> int *) let () = assert (typeof (Fun ("x", App (Op "+", Pair (Var "x", Var "x")))) = Tarrow (Tint, Tint)) (* let x = 1 in x+x : int *) let () = assert (typeof (Let ("x", Const 1, App (Op "+", Pair (Var "x", Var "x")))) = Tint) (* let id = fun x -> x in id 1 *) let () = assert (typeof (Let ("id", Fun ("x", Var "x"), App (Var "id", Const 1))) = Tint) (* let id = fun x -> x in id id 1 *) let () = assert (typeof (Let ("id", Fun ("x", Var "x"), App (App (Var "id", Var "id"), Const 1))) = Tint) (* let id = fun x -> x in (id 1, id (1,2)) : int * (int * int) *) let () = assert (typeof (Let ("id", Fun ("x", Var "x"), Pair (App (Var "id", Const 1), App (Var "id", Pair (Const 1, Const 2))))) = Tproduct (Tint, Tproduct (Tint, Tint))) (* app = fun f x -> let y = f x in y : ('a -> 'b) -> 'a -> 'b *) let () = let ty = typeof (Fun ("f", Fun ("x", Let ("y", App (Var "f", Var "x"), Var "y")))) in assert (match ty with | Tarrow (Tarrow (Tvar v1, Tvar v2), Tarrow (Tvar v3, Tvar v4)) -> V.equal v1 v3 && V.equal v2 v4 | _ -> false)
Alguns testes negativos :
let cant_type e = try let _ = typeof e in false with UnificationFailure _ -> true (* 1 2 *) let () = assert (cant_type (App (Const 1, Const 2))) (* fun x -> x x *) let () = assert (cant_type (Fun ("x", App (Var "x", Var "x")))) (* (fun f -> +(f 1)) (fun x -> x) *) let () = assert (cant_type (App (Fun ("f", App (Op "+", App (Var "f", Const 1))), Fun ("x", Var "x")))) (* fun x -> (x 1, x (1,2)) *) let () = assert (cant_type (Fun ("x", Pair (App (Var "x", Const 1), App (Var "x", Pair (Const 1, Const 2)))))) (* fun x -> let z = x in (z 1, z (1,2)) *) let () = assert (cant_type (Fun ("x", Let ("z", Var "x", Pair (App (Var "z", Const 1), App (Var "z", Pair (Const 1, Const 2))))))) (* let distr_pair = fun f -> (f 1, f (1,2)) in distr_pair (fun x -> x) *) let () = assert (cant_type (Let ("distr_pair", Fun ("f", Pair (App (Var "f", Const 1), App (Var "f", Pair (Const 1, Const 2)))), App (Var "distr_pair", (Fun ("x", Var "x"))))))