我正在使用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_Bool
或Equals(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
但是我试图找到一种更优雅的方式来做到这一点。有什么建议吗?
答案 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)
嗯,接受挑战。
首先,由于您的表达式中已有value
和Int
,因此您不需要您的类型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)赋值器