【问题标题】:Simple lambda calculus DSL using GADTs in OCaml在 OCaml 中使用 GADT 的简单 lambda 演算 DSL
【发布时间】:2014-05-05 19:09:40
【问题描述】:

如何使用 GADT 在 OCaml 中定义一个简单的类似于 lambda 演算的 DSL?具体来说,我无法弄清楚如何正确定义类型检查器以将无类型 AST 转换为有类型 AST,也无法确定上下文和环境的正确类型。

这里是使用 OCaml 中的传统方法的类似 lambda 演算的简单语言的一些代码

(* Here's a traditional implementation of a lambda calculus like language *)

type typ =
| Boolean
| Integer
| Arrow of typ*typ

type exp =
| Add of exp*exp
| And of exp*exp
| App of exp*exp
| Lam of string*typ*exp
| Var of string
| Int of int
| Bol of bool

let e1=Add(Int 1,Add(Int 2,Int 3))
let e2=Add(Int 1,Add(Int 2,Bol false)) (* Type error *)
let e3=App(Lam("x",Integer,Add(Var "x",Var "x")),Int 4)

let rec typecheck con e =
    match e with
    | Add(e1,e2) ->
        let t1=typecheck con e1 in
        let t2=typecheck con e2 in
        begin match (t1,t2) with 
        | (Integer,Integer) -> Integer
        | _ -> failwith "Tried to add with something other than Integers"
        end
    | And(e1,e2) ->
        let t1=typecheck con e1 in
        let t2=typecheck con e2 in
        begin match (t1,t2) with 
        | (Boolean,Boolean) -> Boolean 
        | _ -> failwith "Tried to and with something other than Booleans"
        end
    | App(e1,e2) ->
        let t1=typecheck con e1 in
        let t2=typecheck con e2 in
        begin match t1 with 
        | Arrow(t11,t12) ->
            if t11 <> t2 then
                failwith "Mismatch of types on a function application"
            else
                t12
        | _ -> failwith "Tried to apply a non-arrow type" 
        end
    | Lam(x,t,e) ->
        Arrow (t,typecheck ((x,t)::con) e)
    | Var x  ->
        let (y,t) = List.find (fun (y,t)->y=x) con in
        t
    | Int _ -> Integer
    | Bol _ -> Boolean

let t1 = typecheck [] e1
(* let t2 = typecheck [] e2 *)
let t3 = typecheck [] e3

type value = 
| VBoolean of bool
| VInteger of int
| VArrow of ((string*value) list -> value -> value)

let rec eval env e = 
    match e with
    | Add(e1,e2) ->
        let v1=eval env e1 in
        let v2=eval env e2 in
        begin match (v1,v2) with 
        | (VInteger i1,VInteger i2) -> VInteger (i1+i2) 
        | _ -> failwith "Tried to add with something other than Integers"
        end
    | And(e1,e2) ->
        let v1=eval env e1 in
        let v2=eval env e2 in
        begin match (v1,v2) with 
        | (VBoolean b1,VBoolean b2) -> VBoolean (b1 && b2) 
        | _ -> failwith "Tried to and with something other than Booleans"
        end
    | App(e1,e2) ->
        let v1=eval env e1 in
        let v2=eval env e2 in
        begin match v1 with 
        | VArrow a1 -> a1  env v2 
        | _ -> failwith "Tried to apply a non-arrow type" 
        end
    | Lam(x,t,e) ->
        VArrow (fun env' v' -> eval ((x,v')::env') e) 
    | Var x  ->
        let (y,v) = List.find (fun (y,t)->y=x) env in
        v 
    | Int i -> VInteger i 
    | Bol b -> VBoolean b

let v1 = eval [] e1
let v3 = eval [] e3

现在,我正在尝试将其转换为使用 GADT 的内容。这是我的开始

(* Now, we try to GADT the process *) 

type exp =
| Add of exp*exp
| And of exp*exp
| App of exp*exp
| Lam of string*typ*exp
| Var of string
| Int of int
| Bol of bool

let e1=Add(Int 1,Add(Int 2,Int 3))
let e2=Add(Int 1,Add(Int 2,Bol false))
let e3=App(Lam("x",Integer,Add(Var "x",Var "x")),Int 4)

type _ texp =
| TAdd : int texp * int texp -> int texp
| TAnd : bool texp * bool texp -> bool texp
| TApp : ('a -> 'b) texp * 'a texp -> 'b texp
| TLam : string*'b texp -> ('a -> 'b) texp
| TVar : string -> 'a texp
| TInt : int -> int texp
| TBol : bool -> bool texp

let te1 = TAdd(TInt 1,TAdd(TInt 2,TInt 3))

let rec typecheck : type a. exp -> a texp = fun e ->
    match e with
    | Add(e1,e2) ->
        let te1 = typecheck e1 in
        let te2 = typecheck e2 in
        TAdd (te1,te2)
    | _ -> failwith "todo"

这就是问题所在。首先,我不确定如何在类型 texp 中为 TLam 和 TVar 定义正确的类型。通常,我会为类型提供变量名,但我不确定如何在这种情况下执行此操作。其次,我不知道函数类型检查中上下文的正确类型。以前,我使用某种列表,但现在我确定列表的类型。第三,在省略上下文之后,类型检查函数本身不会进行类型检查。它失败并显示消息

File "test03.ml", line 32, characters 8-22:
Error: This expression has type int texp
       but an expression was expected of type a texp
       Type int is not compatible with type a 

这完全有道理。这更像是一个我不确定类型检查的正确类型应该是什么的问题。

无论如何,您如何修复这些功能?


编辑 1

这是上下文或环境的可能类型

type _ ctx =
| Empty : unit ctx
| Item :  string * 'a * 'b ctx -> ('a*'b) ctx

编辑 2

环境的诀窍是确保环境的类型嵌入到表达式的类型中。否则,没有足够的信息来确保内容的类型安全。这是一个完整的解释器。目前,我没有一个有效的类型检查器来从无类型表达式移动到有类型表达式。

type (_,_) texp =
| TAdd : ('e,int) texp * ('e,int) texp -> ('e,int) texp
| TAnd : ('e,bool) texp * ('e,bool) texp -> ('e,bool) texp
| TApp : ('e,('a -> 'b)) texp * ('e,'a) texp -> ('e,'b) texp
| TLam : (('a*'e),'b) texp -> ('e,('a -> 'b)) texp
| TVar0 : (('a*'e),'a) texp
| TVarS : ('e,'a) texp -> (('b*'e),'a) texp
| TInt : int -> ('e,int) texp
| TBol : bool -> ('e,bool) texp

let te1 = TAdd(TInt 1,TAdd(TInt 2,TInt 3))
(*let te2 = TAdd(TInt 1,TAdd(TInt 2,TBol false))*)
let te3 = TApp(TLam(TAdd(TVar0,TVar0)),TInt 4)
let te4 = TApp(TApp(TLam(TLam(TAdd(TVar0,TVarS(TVar0)))),TInt 4),TInt 5)
let te5 = TLam(TLam(TVarS(TVar0)))

let rec eval : type e t. e -> (e,t) texp -> t = fun env e -> 
    match e with
    | TAdd (e1,e2) ->
        let v1 = eval env e1 in
        let v2 = eval env e2 in
        v1 + v2
    | TAnd (e1,e2) ->
        let v1 = eval env e1 in
        let v2 = eval env e2 in
        v1 && v2
    | TApp (e1,e2) ->
        let v1 = eval env e1 in
        let v2 = eval env e2 in
        v1 v2
    | TLam e ->
        fun x -> eval (x,env) e 
    | TVar0 ->
        let (v,vs)=env in
        v
    | TVarS e ->
        let (v,vs)=env in
        eval vs e 
    | TInt i -> i
    | TBol b -> b

那么,我们有

# eval () te1;;
- : int = 6
# eval () te3;;
- : int = 8
# eval () te5;;
- : '_a -> '_b -> '_a = <fun>
# eval () te4;;
- : int = 9

【问题讨论】:

标签: ocaml dsl lambda-calculus gadt


【解决方案1】:

如果您希望术语表示强制类型良好,则需要更改类型环境(和变量)的表示方式:您不能精细地键入从字符串到值的映射(表示映射的类型是同质的)。经典的解决方案是使用De Bruijn indices(强类型数字)而不是变量名来表示变量。它可能会帮助您先在无类型世界中执行该转换,然后只关心在无类型 -> GADT 中输入。

这是一个粗略的强类型变量的 GADT 声明:

type (_, _) var =
  | Z : ('a, 'a * 'g) var
  | S : ('a, 'g) var -> ('a, 'b * 'g) var

('a, 'g) var 类型的值应理解为从'g 类型的环境中提取'a 类型的值的方法的描述。环境由级联的右嵌套元组表示。 Z 案例对应于选择环境中的第一个变量,而 S 案例忽略了最顶层的变量并在环境中看起来更深。

Shayan Najd 有一个(Haskell)实现这个想法on github。请随意查看the GADT representationthe type-checking/translating code

【讨论】:

    【解决方案2】:

    好的,所以我终于解决了问题。由于我可能不是唯一一个觉得这很有趣的人,这里有一套完整的代码,可以进行类型检查和评估:

    type (_,_) texp =
    | TAdd : ('gamma,int) texp * ('gamma,int) texp -> ('gamma,int) texp
    | TAnd : ('gamma,bool) texp * ('gamma,bool) texp -> ('gamma,bool) texp
    | TApp : ('gamma,('t1 -> 't2)) texp * ('gamma,'t1) texp -> ('gamma,'t2) texp
    | TLam : (('gamma*'t1),'t2) texp -> ('gamma,('t1 -> 't2)) texp
    | TVar0 : (('gamma*'t),'t) texp
    | TVarS : ('gamma,'t1) texp -> (('gamma*'t2),'t1) texp
    | TInt : int -> ('gamma,int) texp
    | TBol : bool -> ('gamma,bool) texp
    
    type _ typ =
    | Integer : int typ
    | Boolean : bool typ
    | Arrow : 'a typ * 'b typ -> ('a -> 'b) typ
    
    type (_,_) iseq = IsEqual : ('a,'a) iseq
    let rec is_equal : type a b. a typ -> b typ -> (a,b) iseq option = fun a b ->
        match a, b with
        | Integer, Integer -> Some IsEqual
        | Boolean, Boolean -> Some IsEqual
        | Arrow(t1,t2), Arrow(u1,u2) ->
            begin match is_equal t1 u1, is_equal t2 u2 with
            | Some IsEqual, Some IsEqual -> Some IsEqual
            | _ -> None
            end
        | _ -> None
    
    type _ isint = IsInt : int isint
    let is_integer : type a. a typ -> a isint option = fun a -> 
        match a with
        | Integer -> Some IsInt
        | _ -> None
    
    type _ isbool = IsBool : bool isbool
    let is_boolean : type a. a typ -> a isbool option = fun a -> 
        match a with
        | Boolean -> Some IsBool 
        | _ -> None
    
    type _ context =
    | CEmpty : unit context 
    | CVar : 'a context * 't typ -> ('a*'t) context 
    
    type exp =
    | Add of exp*exp
    | And of exp*exp
    | App of exp*exp
    | Lam : 'a typ * exp -> exp
    | Var0
    | VarS of exp
    | Int of int
    | Bol of bool
    
    type _ exists_texp =
    | Exists : ('gamma,'t) texp * 't typ -> 'gamma exists_texp
    
    let rec typecheck
        : type gamma t. gamma context -> exp -> gamma exists_texp =
    fun ctx e ->
        match e with
        | Int i -> Exists ((TInt i) , Integer)
        | Bol b -> Exists ((TBol b) , Boolean)
        | Var0 ->
            begin match ctx with
            | CEmpty -> failwith "Tried to grab a nonexistent variable"
            | CVar(ctx,t) -> Exists (TVar0 , t)
            end
        | VarS e ->
            begin match ctx with
            | CEmpty -> failwith "Tried to grab a nonexistent variable"
            | CVar(ctx,_) ->
                let tet = typecheck ctx e in
                begin match tet with
                | Exists (te,t) -> Exists ((TVarS te) , t)
                end
            end
        | Lam(t1,e) ->
            let tet2 = typecheck (CVar (ctx,t1)) e in
            begin match tet2 with
            | Exists (te,t2) -> Exists ((TLam te) , (Arrow(t1,t2)))
            end
        | App(e1,e2) ->
            let te1t1 = typecheck ctx e1 in
            let te2t2 = typecheck ctx e2 in
            begin match te1t1,te2t2 with
            | Exists (te1,t1),Exists (te2,t2) ->
                begin match t1 with
                | Arrow(t11,t12) ->
                    let p = is_equal t11 t2 in
                    begin match p with
                    | Some IsEqual -> 
                        Exists ((TApp (te1,te2)) , t12)
                    | None -> 
                        failwith "Mismatch of types on a function application"
                    end
                | _ -> failwith "Tried to apply a non-arrow type" 
                end
            end
        | Add(e1,e2) ->
            let te1t1 = typecheck ctx e1 in
            let te2t2 = typecheck ctx e2 in
            begin match te1t1,te2t2 with
            | Exists (te1,t1),Exists (te2,t2) ->
                let p = is_equal t1 t2 in
                let q = is_integer t1 in
                begin match p,q with
                | Some IsEqual, Some IsInt ->
                    Exists ((TAdd (te1,te2)) , t1)
                | _ ->
                    failwith "Tried to add with something other than Integers"
                end
            end
        | And(e1,e2) ->
            let te1t1 = typecheck ctx e1 in
            let te2t2 = typecheck ctx e2 in
            begin match te1t1,te2t2 with
            | Exists (te1,t1),Exists (te2,t2) ->
                let p = is_equal t1 t2 in
                let q = is_boolean t1 in
                begin match p,q with
                | Some IsEqual, Some IsBool ->
                    Exists ((TAnd (te1,te2)) , t1)
                | _ ->
                    failwith "Tried to and with something other than Booleans"
                end
            end
    
    let e1 = Add(Int 1,Add(Int 2,Int 3))
    let e2 = Add(Int 1,Add(Int 2,Bol false))
    let e3 = App(Lam(Integer,Add(Var0,Var0)),Int 4)
    let e4 = App(App(Lam(Integer,Lam(Integer,Add(Var0,VarS(Var0)))),Int 4),Int 5)
    let e5 = Lam(Integer,Lam(Integer,VarS(Var0)))
    let e6 = App(Lam(Integer,Var0),Int 1)
    let e7 = App(Lam(Integer,Lam(Integer,Var0)),Int 1)
    let e8 = Lam(Integer,Var0)
    let e9 = Lam(Integer,Lam(Integer,Var0))
    
    let tet1 = typecheck CEmpty e1
    (*let tet2 = typecheck CEmpty e2*)
    let tet3 = typecheck CEmpty e3
    let tet4 = typecheck CEmpty e4
    let tet5 = typecheck CEmpty e5
    let tet6 = typecheck CEmpty e6
    let tet7 = typecheck CEmpty e7
    let tet8 = typecheck CEmpty e8
    let tet9 = typecheck CEmpty e9
    
    let rec eval : type gamma t. gamma -> (gamma,t) texp -> t = fun env e -> 
        match e with
        | TAdd (e1,e2) ->
            let v1 = eval env e1 in
            let v2 = eval env e2 in
            v1 + v2
        | TAnd (e1,e2) ->
            let v1 = eval env e1 in
            let v2 = eval env e2 in
            v1 && v2
        | TApp (e1,e2) ->
            let v1 = eval env e1 in
            let v2 = eval env e2 in
            v1 v2
        | TLam e ->
            fun x -> eval (env,x) e 
        | TVar0 ->
            let (env,x)=env in
            x
        | TVarS e ->
            let (env,x)=env in
            eval env e 
        | TInt i -> i
        | TBol b -> b
    
    type exists_v =
    | ExistsV : 't -> exists_v
    
    let typecheck_eval e =
        let tet = typecheck CEmpty e in
        match tet with
        | Exists (te,t) -> ExistsV (eval () te)
    
    let v1 = typecheck_eval e1
    let v3 = typecheck_eval e3
    let v4 = typecheck_eval e4
    let v5 = typecheck_eval e5
    let v6 = typecheck_eval e6
    let v7 = typecheck_eval e7
    let v8 = typecheck_eval e8
    let v9 = typecheck_eval e9
    

    以下是我遇到的问题以及我如何解决它们

    1. 为了正确键入类型表达式 texp,需要将环境的类型构建到 texp 的类型中。正如加什正确指出的那样,这意味着我们需要某种 De Bruijin 符号。最简单的就是 Var0 和 VarS。为了使用变量名,我们只需要预处理 AST。
    2. 表达式的类型 typ 需要包含要匹配的变体类型以及我们在类型化表达式中使用的类型。换句话说,这也需要是 GADT。
    3. 我们需要三个证明才能在类型检查器中找出正确的类型。它们是 is_equal、is_integer 和 is_bool。 is_equal 的代码实际上在 Advanced examples 下的 OCaml 手册中。具体看eq_type的定义。
    4. 对于无类型的 AST,exp 类型实际上也需要是 GADT。 lambda 抽象需要访问 typ,这是一个 GADT。
    5. 类型检查器返回类型化表达式和类型的存在类型。我们都需要让程序检查类型。此外,我们需要存在主义,因为无类型表达式可能有也可能没有类型。
    6. existential 类型exists_texp 公开环境/上下文的类型,但不公开类型。为了正确进行类型检查,我们需要公开此类型。
    7. 一切设置完成后,评估器将完全遵循类型规则。
    8. 类型检查器与评估器组合的结果必须是另一个存在类型。先验,我们不知道结果类型,所以我们必须将其隐藏在存在包中。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-07-14
      • 2013-03-26
      • 1970-01-01
      • 2018-08-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多