Suppress exhaustive matching warning in OCaml

2019-06-16 05:15发布

问题:

I'm having a problem in fixing a warning that OCaml compiler gives to me.

Basically I'm parsing an expression that can be composed by Bool, Int and Float.

I have a symbol table that tracks all the symbols declared with their type:

type ast_type = Bool | Int | Float
and variables = (string, int*ast_type) Hashtbl.t;

where int is the index used later in the array of all variables.

I have then a concrete type representing the value in a variable:

type value =
  | BOOL of bool
  | INT of int
  | FLOAT of float
  | UNSET
and var_values = value array

I'm trying to define the behaviour of a variable reference inside a boolean expression so what I do is

  • check that the variable is declared
  • check that the variable has type bool

to do this I have this code (s is the name of the variable):

| GVar s ->
            begin
                try
                    let (i,t) = Hashtbl.find variables s in
                        if (t != Bool) then
                            raise (SemanticException (BoolExpected,s))
                        else
                            (fun s -> let BOOL v = Array.get var_values i in v)
                with
                    Not_found -> raise (SemanticException (VarUndefined,s)) 
            end

The problem is that my checks assure that the element taken from var_values will be of type BOOL of bool but of course this constraint isn't seen by the compiler that warns me:

Warning P: this pattern-matching is not exhaustive. Here is an example of a value that is not matched: (FLOAT _ |INT _ |UNSET)

How am I supposed to solve this kind of issues? Thanks in advance

回答1:

This is a problem that you can solve using OCaml's polymorphic variants.

Here is some compilable OCaml code that I infer exhibits your problem:

type ast_type = Bool | Int | Float
and variables = (string, int*ast_type) Hashtbl.t

type value =
  | BOOL of bool
  | INT of int
  | FLOAT of float
  | UNSET

and var_values = value array

type expr = GVar of string

type exceptioninfo = BoolExpected | VarUndefined

exception SemanticException of exceptioninfo * string

let variables = Hashtbl.create 13

let var_values = Array.create 13 (BOOL false)

let f e = 
  match e with
  | GVar s ->
    begin
        try
        let (i,t) = Hashtbl.find variables s in
            if (t != Bool) then
            raise (SemanticException (BoolExpected,s))
            else
            (fun s -> let BOOL v = Array.get var_values i in v)
        with
        Not_found -> raise (SemanticException (VarUndefined,s)) 
    end

It generates the warning:

File "t.ml", line 30, characters 42-48:
Warning P: this pattern-matching is not exhaustive.
Here is an example of a value that is not matched:
(FLOAT _|INT _|UNSET)

Here is the same code transformed to use polymorphic variants. That code compiles without warnings. Note that polymorphic variants have more expressive power than standard types (here allowing to express that var_values is an array of BOOL only), but they can lead to puzzling warnings.

type ast_type = Bool | Int | Float
and variables = (string, int*ast_type) Hashtbl.t

type value =
  [ `BOOL of bool
  | `INT of int
  | `FLOAT of float
  | `UNSET ]

and var_values = value array

type expr = GVar of string

type exceptioninfo = BoolExpected | VarUndefined

exception SemanticException of exceptioninfo * string

let variables = Hashtbl.create 13

let var_values = Array.create 13 (`BOOL false)

let f e = 
  match e with
  | GVar s ->
    begin
        try
        let (i,t) = Hashtbl.find variables s in
            if (t != Bool) then
            raise (SemanticException (BoolExpected,s))
            else
            (fun s -> let `BOOL v = Array.get var_values i in v)
        with
        Not_found -> raise (SemanticException (VarUndefined,s)) 
    end

Here are the types inferred by OCaml on the above code:

type ast_type = Bool | Int | Float
and variables = (string, int * ast_type) Hashtbl.t
type value = [ `BOOL of bool | `FLOAT of float | `INT of int | `UNSET ]
and var_values = value array
type expr = GVar of string
type exceptioninfo = BoolExpected | VarUndefined
exception SemanticException of exceptioninfo * string
val variables : (string, int * ast_type) Hashtbl.t
val var_values : [ `BOOL of bool ] array
val f : expr -> 'a -> bool


回答2:

Take a look at this and search for "disable warnings". You should come to a flag -w.

If you want to fix it the "ocamlish" way, then I think you must make the pattern match exhaustive, i.e. cover all cases that might occur.

But if you don't want to match against all possible values, you might consider using wildcard (see here), that covers all cases you do not want to handle explicitly.



回答3:

In this particular case, polymorphic variants, as explained by Pascal, are a good answer.

Sometimes, however, you're stuck with an impossible case. Then I find it natural to write

(fun s -> match Array.get var_values i with
            | BOOL v -> v
            | _ -> assert false)

This is much better than using the -w p flag which could hide other, undesired non-exhaustive pattern matches.



回答4:

Whoops! Misread your question. Leaving my answer below for posterity.

Updated answer: is there a reason why you are doing the check in the hashtbl, or why you can't have the concrete data types (type value) in the hashtbl? That would simplify things. As it is, you can move the check for bool to the Array.get and use a closure:

| GVar s ->
        begin
            try
                let (i,_) = Hashtbl.find variables s in
                    match (Array.get var_values i) with BOOL(v) -> (fun s -> v)
                    | _ -> raise (SemanticException (BoolExpected,s))
            with
                Not_found -> raise (SemanticException (VarUndefined,s)) 
        end

Alternatively I think it would make more sense to simplify your code. Move the values into the Hashtbl instead of having a type, an index and an array of values. Or just store the index in the Hashtbl and check the type in the array.

INCORRECT ANSWER BELOW:

You can replace the if else with a match. Or you can replace the let with a match:

replace if/else:

| GVar s ->
        begin
            try
                let (i,t) = Hashtbl.find variables s in
                    match t with Bool -> (fun s -> let BOOL v = Array.get var_values i in v)
                    | _ -> raise (SemanticException (BoolExpected,s))
            with
                Not_found -> raise (SemanticException (VarUndefined,s)) 
        end

replace let:

| GVar s ->
        begin
            try
                match (Hashtbl.find variables s) with (i, Bool) -> (fun s -> let BOOL v = Array.get var_values i in v)
                    | _ -> raise (SemanticException (BoolExpected,s))
            with
                Not_found -> raise (SemanticException (VarUndefined,s)) 
        end