-
Notifications
You must be signed in to change notification settings - Fork 4
How to: removing exceptions using Choice _, _
For example, this function
let dest_type = function
| (Tyapp(s, ty)) -> s, ty
| (Tyvar _) -> failwith "dest_type: type variable not a constructor"
is transformed to
let dest_type = function
| (Tyapp(s, ty)) -> Choice.succeed (s, ty)
| (Tyvar _) -> Choice.failwith "dest_type: type variable not a constructor"
where two possible paths are of type Choice<hol_type, exn>
and no exception is raised.
To illustrate, this function
let rec type_of tm =
match tm with
| Var(_, ty) -> ty
| Const(_, ty) -> ty
| Comb(s, _) -> hd(tl(snd(dest_type(type_of s))))
| Abs(Var(_, ty), t) ->
Tyapp("fun", [ty; type_of t])
| _ -> failwith "type_of: not a type of a term"
is turned into
let type_of tm =
let rec type_of tm =
match tm with
| Var(_, ty) -> ty
| Const(_, ty) -> ty
| Comb(s, _) -> hd(tl(snd(Choice.get <| dest_type(type_of s))))
| Abs(Var(_, ty), t) ->
Tyapp("fun", [ty; type_of t])
| _ -> failwith "type_of: not a type of a term"
try
Choice.succeed <| type_of tm
with Failure s ->
Choice.failwith s
Notice that the original type_of
function becomes an inner function and we catch all possible exceptions in outer function. Why should we do so? The reason is that Choice.get
, hd
and tl
functions can throw exceptions. So we have to explicitly handle each of these functions if we would like to avoid using try/with
. Remember that this is a quick-and-dirty approach; we should switch to other approaches for better performance.
When we do conversion on core modules, we insert Choice.get
at call sites. At some point, outer modules will contain full of Choice.get
calls. We can use choice
computation expression in this scenario.
This example
let alpha v tm =
let v0, bod =
try
Choice.get <| dest_abs tm
with
| Failure _ as e ->
nestedFailwith e "alpha: Not an abstraction"
if v = v0 then tm
elif Choice.get <| type_of v = (Choice.get <| type_of v0) && not(vfree_in v bod) then Choice.get <| mk_abs(v, Choice.get <| vsubst [v, v0] bod)
else failwith "alpha: Invalid new variable"
is transformed to
let alpha v tm =
choice {
let! (v0, bod) =
dest_abs tm
|> Choice.bindError (fun e -> Choice.nestedFailwith e "alpha: Not an abstraction")
if v = v0 then return tm
else
let! ty = type_of v
let! ty0 = type_of v0
if ty = ty0 && not(vfree_in v bod) then
let! sub = vsubst [v, v0] bod
return! mk_abs(v, sub)
else return! Choice.failwith "alpha: Invalid new variable"
}
Inside a choice
computation expression:
-
let!
allows us to implicitly get'T
value fromChoice<'T, exn>
. If an error case is supplied, the code block after this point isn't executed. -
return!
allows us to return aChoice<'T, exn>
value inside computation expression. -
return
is the facility to lift a'T
value toChoice<'T, exn>
(as easy as an implicitChoice.succeed
call). - we see that all branches have the same
Choice<'T, exn>
return type and we get rid of all nastyChoice.get
occurrences.