Abdallah
Abdallah

Reputation: 335

Polymorphic exception arguments in OCaml

In OCaml, one can define their own exceptions and these can take arguments, as in the following snippet.

exception there_is_a_problem of string

I wonder if there is a way to use exceptions that are polymorphic in their arguments. An example application would be to shortcut traversing a datastructure. E.g., I'd like to be able to write something along the following lines.

exception Found_it of 'a
let find_opt test l =
  let aux elt = if test elt then raise (Found_it elt) in
  try List.iter aux l; None with
  | Foundit b -> Some b

My actual datastructure is more complicated than a list and I prefer to use an iterator to traverse it, so I cannot write find_opt like the stdlib List.find_opt. My current solution is to use a reference as below. I find the above style more elegant but I'm mostly just feeling curious now. Another solution I have is to define a new generic fold iterator that can shortcut the computation if some input test is satisfied, but this requires access to the implementation of the datastructure.

let find_opt' test l =
  let store = ref None in
  let aux elt = if test elt then (store := Some elt; raise Exit) in
  (try List.iter aux l with Exit -> ());
  !store

Upvotes: 1

Views: 702

Answers (2)

Jeffrey Scofield
Jeffrey Scofield

Reputation: 66823

This code seems to be pretty close to what you're asking for:

let f (type a) test (l: a list) =
  let exception F of a in
  let aux elt = if test elt then raise (F elt) in
  try List.iter aux l; None
  with F b -> Some b

Possibly there's a simpler way, but this is what I came up with.

Update

This code uses a locally abstract type, which are described in Chapter 10.4 of the OCaml manual.

While reading the manual just now, I noticed that it suggests using locally abstract types for exactly the case you are asking for! This makes me more confident that my answer is pretty good :-)

Upvotes: 4

octachron
octachron

Reputation: 18902

Another option is to use a with_return function (Base has one for instance):

let find_opt' test l =
  with_return ( fun {return} ->
  List.iter (fun elt -> if test elt then return (Some elt)) l; None
  )

The trick is to make the with_return function defines a new exception (with a locally abstract) and provides a polymorphic function that raises the new exception to the user:

type 'a return = { return: 'never. 'a -> 'never }
let with_return (type a) f =
  let exception Return of a in
  let return x = raise (Return x) in
  try f {return} with Return x -> x

The type return may look strange, but it express the fact that the inner return function never returns to the current context, and always raises an exception. Being that precise makes it possible to use the return function in more contexts. For instance, in the example belows

let l = with_return (fun {return} ->
  let integer = if test () then return () else 1 in
  let list = if test () then return () else [1] in
  integer :: list
)

return is first used in a context where it is expected to return an int and then it is used in a context where an int list is expected. Without the explicitly polymorphic annotation this would be a type error.

Upvotes: 1

Related Questions