Q:

Type checking with try/with statements in Ocaml?

I'm implementing a mock interpreter using Ocaml. One of the mock functionalities is to evaluate a made up expression using pattern matching. The issue is trivial and definitely fixable, but I'm trying to find a more elegant way of doing this, so read on if you're still interested!
A snippet of my user defined type looks like this:
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

Of course, I have a function for evaluating these expressions in the form of (string * value) list -> expr -> value, a snippet of the function looks like this:
(* 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"))
;;

Here I'm using try/with statements to catch any native error types and throwing my own error TypeError. I'm using polymorphic variants like Val_Int n1 to protect my variables from invalid type operations like 1 + true, which should throw TypeError. 
The issue is more for Equals expressions. Equals should only be evaluated when the two arguments are of the same type (i.e. both Val_Int or both Val_Bool), and throw TypeError if something like Equals(Val_Int(0), Val_Bool(false)) is passed in. 
With Plus, I'm able to explicitly define my types as Val_Int, so something like Plus(Val_Int(0), Val_Bool(false)) would throw match failure and it would be caught by the try/with statement, but I can't do this for Equals, which could be of either Val_Int or Val_Bool. That is, something like Equals(Val_Int(0), Val_Bool(false)) will only return Val_Bool(false) instead of throwing an error.
One way I could fix this is if I used match/with statements instead of try/with, like if I did something like:
|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"))

but I'm trying to find a more elegant way of doing this. Any Suggestions?

A:

Well, challenge accepted.
First of all, you don't need your type value since you already have Int and Bool in your expression. But you could keep this type value so it has a type in it, and that's what I'd do :
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")

Yes, in that case the type is only used once, in case of an Equal construct, but if you want a more complex system you'll just have to add more types and it will work well.
Notice that in the Plus construct I match the expressions, not the types, because I need the values in it.
I took a moment to use a Map and not a List for your lookup function.

