letrec eval : type a. a term -> a = function | Int n -> n (* a = int *) | Add -> (fun x y -> x+y) (* a = int -> int -> int *) | App(f,x) -> (eval f) (eval x) (* eval 在类型 (b->a) 和 b 上被调用,用于新的 b *)
GADTs 的类型推断非常困难。这是因为当类型从一个分支中逃逸时,某些类型可能会变得模棱两可。例如,在上面的 Int 情况下,n 可能是 int 或 a 类型,并且它们在该分支之外并不等效。作为一种初步近似,如果模式匹配用不包含自由类型变量的类型(在被匹配项和返回类型上)进行注释,则类型推断将始终有效。这在上面的示例中是有效的,因为类型注释仅包含局部抽象类型。
我们上面定义的 term 类型是一个索引类型,其中一个类型参数反映了值内容的属性。GADTs 的另一个用途是单例类型,其中一个 GADT 值代表一个确切的类型。此值可以用作此类型的运行时表示,并且接收它的函数可以具有多态行为。
这是一个多态函数的示例,它获取一些类型 t 的运行时表示和相同类型的值,然后将值漂亮地打印为字符串
type _ typ = | Int : int typ | String : string typ | Pair : 'a typ * 'b typ -> ('a * 'b) typ letrec to_string: type t. t typ -> t -> string = fun t x -> match t with | Int -> Int.to_string x | String -> Printf.sprintf "%S" x | Pair(t1,t2) -> let (x1, x2) = x in Printf.sprintf "(%s,%s)" (to_string t1 x1) (to_string t2 x2)
GADTs 的另一个常见应用是等式见证。
type (_,_) eq = Eq : ('a,'a) eq let cast : type a b. (a,b) eq -> a -> b = fun Eq x -> x
这里类型 eq 只有一个构造函数,通过匹配它,可以添加一个本地约束,允许在 a 和 b 之间进行转换。通过构建这样的等式见证,可以使语法上不同的类型相等。
以下是一个使用单例类型和等式见证来实现动态类型的示例。
letrec eq_type : type a b. a typ -> b typ -> (a,b) eq option = fun a b -> match a, b with | Int, Int -> Some Eq | String, String -> Some Eq | Pair(a1,a2), Pair(b1,b2) -> beginmatch eq_type a1 b1, eq_type a2 b2 with | Some Eq, Some Eq -> Some Eq | _ -> None end | _ -> None type dyn = Dyn : 'a typ * 'a -> dyn let get_dyn : type a. a typ -> dyn -> a option = fun a (Dyn(b,x)) -> match eq_type a b with | None -> None | Some Eq -> Some x
type any = Any : 'name -> any let escape (Any x) = x
Error: 此表达式类型为 $name,但预期表达式类型为 'a 类型构造函数 $name 将逃逸其作用域 提示: $name 是由构造函数 Any 绑定的存在类型。
$'a 如果存在变量在类型推断过程中与类型变量 'a 统一
type ('arg,'result,'aux) fn = | Fun: ('a ->'b) -> ('a,'b,unit) fn | Mem1: ('a ->'b) * 'a * 'b -> ('a, 'b, 'a * 'b) fn let apply: ('arg,'result, _ ) fn -> 'arg -> 'result = fun f x -> match f with | Fun f -> f x | Mem1 (f,y,fy) -> if x = y then fy else f x