使用Ocaml中的try / with语句进行类型检查?

时间:2017-03-11 21:49:46

标签: pattern-matching ocaml

我正在使用Ocaml实现模拟解释器。模拟功能之一是使用模式匹配来评估组合表达式。 这个问题是微不足道的,绝对可以修复,但我试图找到一种更优雅的方式,所以请继续阅读,如果您仍然感兴趣的话!

我的用户定义类型的片段如下所示:

type value =
  | Val_Int of int
  | Val_Bool of bool

type expr =
  | Id of string
  | Int of int
  | Bool of bool
  | Plus of expr * expr
  | Equal of expr * expr

当然,我有一个以(string * value) list -> expr -> value的形式评估这些表达式的函数,该函数的片段如下所示:

(* Ignore this helper function if you'd like *)
let rec lookup env x = match env with
  | [] -> raise (DeclarationError "Declaration Error")
  | (y,v)::env_t -> if x = y then v else lookup env_t x
;;

(* The evaluation function *)
let rec eval_expr env e = match e with
  | Id(x) -> lookup env x
  | Int(x) -> Val_Int(x)
  | Bool(x) -> Val_Bool(x)
  | Plus(x,y) ->  (try
                    let Val_Int n1 = eval_expr env x in
                    let Val_Int n2 = eval_expr env y in
                    Val_Int (n1 + n2)
                  with _ -> raise (TypeError "Type Error Plus"))
  | Equal(x,y) -> (try
                    let value n1 = eval_expr env x in
                    let value n2 = eval_expr env y in
                    Val_Bool (n1 = n2)
                  with _ -> raise (TypeError "Type Error Equal"))
;;

我在这里使用try/with语句来捕获任何本机错误类型并抛出我自己的错误TypeError。我使用像Val_Int n1这样的多态变体来保护我的变量免受1 + true之类的无效类型操作的影响,这些操作应该抛出TypeError

Equals表达式的问题更多。 Equals只应在两个参数属于同一类型时(即Val_Int或两者Val_Bool)进行评估,如果TypeError之类的话,则抛出Equals(Val_Int(0), Val_Bool(false))传入。

使用Plus,我可以明确地将我的类型定义为Val_Int,因此Plus(Val_Int(0), Val_Bool(false))之类的内容会抛出match failure,它会被try/with抓住Equals声明,但我无法为Val_Int执行此操作,Val_BoolEquals(Val_Int(0), Val_Bool(false))。也就是说,Val_Bool(false)之类的内容只返回match/with而不是抛出错误。

我可以解决这个问题的一种方法是,如果我使用try/with语句而不是|Equal(x,y)->(match (eval_expr env x,eval_expr env y) with |(Val_Int(a),Val_Int(b)) -> Val_Bool(a = b) |(Val_Bool(a),Val_Bool(b)) -> Val_Bool(a = b) |(_,_) -> raise (TypeError "TypeError Equals")) ,就像我做了类似的事情:

Editor->Colors & Fonts -> Font

但是我试图找到一种更优雅的方式来做到这一点。有什么建议吗?

3 个答案:

答案 0 :(得分:2)

捕捉Match_failure的风格很差,你找对方更正确。

这是一种简单的方法,可以将价值观匹配。用自己的错误消息替换断言。

type value =
  | Val_Int of int
  | Val_Bool of bool

type expr =
  | Id of string
  | Int of int
  | Bool of bool
  | Plus of expr * expr
  | Equal of expr * expr

let rec lookup env x = match env with
  | [] -> assert false
  | (y,v)::env_t -> if x = y then v else lookup env_t x

let int_of_val = function
  | Val_Int n -> n
  | _ -> assert false

let val_equal a b =
  match a, b with
  | Val_Int x, Val_Int y -> x = y
  | Val_Bool x, Val_Bool y -> x = y
  | _, _ -> assert false

let rec eval_expr env = function
  | Id name -> lookup env name
  | Int n -> Val_Int n
  | Bool b -> Val_Bool b
  | Plus (x, y) ->
    Val_Int (int_of_val (eval_expr env x) +
             int_of_val (eval_expr env y))
  | Equal (x,y) ->
    Val_Bool (val_equal (eval_expr env x) (eval_expr env y))

答案 1 :(得分:0)

嗯,接受挑战。

首先,由于您的表达式中已有valueInt,因此您不需要您的类型Bool。但你可以保留这种类型value,因此它有一个类型,这就是我所做的:

type expr =
  | Id of string
  | Int of int
  | Bool of bool
  | Plus of expr * expr
  | Equal of expr * expr

type ty = Int | Bool

(* I used the type expr but we know that we will only put Int or Bool in it *)
type value = { e : expr; t : ty }

exception DeclarationError of string
exception TypeError of string

module SMap = Map.Make (struct 
  type t = string    
  let compare = compare
end)

let rec lookup env x =
  try SMap.find x env
  with Not_found ->raise (DeclarationError "Declaration Error")
;;

let rec eval_expr env e = match e with
  | Id x -> lookup env x
  | Int _ -> {e; t = Int}
  | Bool _ -> {e; t = Bool}
  | Plus (e1, e2) -> 
    let v1 = eval_expr env e1 and
        v2 = eval_expr env e2 in
    begin
      match v1.e, v2.e with
        | Int e1, Int e2 -> {e = Int (e1 + e2); t = Int}
        | _ -> raise (TypeError "Type Error Plus")
    end
  | Equal (e1, e2) -> 
    let v1 = eval_expr env e1 and
        v2 = eval_expr env e2 in
    if v1.t = v2.t then {e = Bool (v1.e = v2.e); t = Bool}
    else raise (TypeError "Type Error Equal")

是的,在这种情况下,类型只使用一次,如果是Equal构造,但如果你想要一个更复杂的系统,你只需要添加更多类型,它就会运行良好

请注意,在Plus构造中,我匹配表达式而不是类型,因为我需要其中的值。

我花了一些时间来为您的Map功能使用List而不是lookup

答案 2 :(得分:0)

另一种方法是使用相互递归的函数: 一个用于算术评估,一个用于布尔评估。 此外,您的expr类型将变为:

type expr = Arith   of aexpr
          | Boolean of bexpr

and aexpr = Int of int
          | Plus of aexpr * aexpr

and bexpr = Bool of bool
          | EqualArith of aexpr * aexpr
          | EqualBool of bexpr * bexpr

这样,Plus只能通过构造接受算术表达式。 类似地,你有一个用于布尔相等的构造函数,以及一个用于算术相等的构造函数。 此外,您必须将评估功能分为两部分:

let rec eval_expr env = function
  | Arith e -> Val_int (eval_aexpr e)
  | Boolean b -> Val_bool (eval_bexpr b)

and eval_aexpr = function
  | Int i    -> i
  | Plus i,j -> (eval_aexpr i) + (eval_aexpr j)

and eval_bexpr = function
  | Bool b        -> b
  | EqualArith (e1,e2) -> 
      let v_e1 = eval_aexpr e1 and v_e2 = eval_aexpr e2 in
      v_e1 = v_e2
  | EqualBool (e1,e2) -> 
      let v_e1 = eval_bexpr e1 and v_e2 = eval_bexpr e2 in
      v_e1 = v_e2

这种编码方式允许您在添加算术(resp boolean)构造时仅修改artithmetical(resp,boolean)赋值器