2017-03-11 59 views
2

我正在實施一個使用Ocaml的模擬解釋器。其中一個模擬功能是使用模式匹配來評估組成表達式。 這個問題是微不足道的,絕對可以解決,但我試圖找到一個更優雅的方式來做到這一點,所以請繼續閱讀,如果你仍然感興趣!在Ocaml中使用try/with語句進行類型檢查?

我的用戶定義類型的代碼片段看起來是這樣的:

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),如果像Equals(Val_Int(0), Val_Bool(false))在傳遞扔TypeError

隨着Plus,我可以明確地定義我的類型作爲Val_Int,所以類似Plus(Val_Int(0), Val_Bool(false))會引起match failure,它會被try/with聲明捕獲,但我不能這樣做Equals,它可能是Val_IntVal_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")) 

,但我試圖找到這樣做的更優雅的方式。有什麼建議麼?

+0

是, 「匹配」是合適的解決方案。 – Bergi

+0

事實上,「匹配」是合適的解決方案,但他想要另一種方式。 「模擬」等同於「虛擬」嗎? – Lhooq

回答

0

好,接受挑戰。

首先,您不需要輸入value,因爲您的表達式中已經有IntBool。但是,你可以保持這種類型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構造中,我匹配表達式而不是類型,因爲我需要其中的值。

我花了一點時間爲lookup函數使用Map而不是List

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)) 
+0

我喜歡這個解決方案。 ;-) – Lhooq

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布爾)您只修改artithmetical(RESP,布爾)評估結構

相關問題