One recent arrival in Core is with_return, a function that lets you return early from a computation. Here's a trivial example:

  1. let sum_until_first_negative list =
  2. with_return (fun r ->
  3. List.fold list ~init:0 ~f:(fun acc x ->
  4. if x >= 0 then acc + x else r.return acc))

One thing that might not be obvious in the above example is what the type of r is. It turns out it's this:

  1. type 'a return = { return : 'b . 'a -> 'b }

The reason for this is to have the return function be truly polymorphic in its return value. That way, like raise, it can be used in any context because its return value will unify with any type.

Note that the sum_until_first_negative example will work even without the record trick, because r.return is used in only one place (and more importantly, with only one type.) But if you want this to work in the general case, you need the record.

So, how does one go about implementing this? Here's the implementation that's currently in Core. Note that we use a locally defined exception, to make sure that only this exception handler can catch the exceptions in question. Also, we use a ref to store the value being returned.

  1. let with_return f =
  2. let module M =
  3. struct exception Return end
  4. in
  5. let r = ref None in
  6. let return = {
  7. return = (fun x ->
  8. r := Some x;
  9. raise M.Return);
  10. }
  11. in
  12. try f return
  13. with M.Return ->
  14. match !r with
  15. | None -> assert false
  16. | Some x -> x

In OCaml 3.12, we can make this code, simpler, safer and more efficient:

  1. let with_return (type t) (f : _ -> t) =
  2. let module M =
  3. struct exception Return of t end
  4. in
  5. let return = { return = (fun x -> raise (M.Return x)); } in
  6. try f return with M.Return x -> x

I'd like to be able to improve this further by getting rid of the overhead of the record, but I suspect it's not possible.

It's worth noting that with_return has its pitfalls. For example, there's no guarantee that the return always terminates the full computation. For example, the following code:

  1. let foo = with_return
  2. (fun r -> try r.return 0 with _ -> 1)

will return 1, not 0. Another interesting behavior is to see what happens when r.return is called outside the scope of the closure passed into with_return. Consider the following convoluted function, which returns an optional function which, when called, calls r.return.

  1. let foo = with_return
  2. (fun r -> Some (fun () -> r.return None))

If you call the function contained in foo, you'll get the following response:

  1. # let f = Option.value_exn foo;;
  2. val f : unit -> 'a = <fun>
  3. # f ();;
  4. Exception: Return 0.

This isn't particularly surprising, once you understand the implementation, and it's semantically fairly reasonable. The only thing you could really ask for beyond this is a warning that the return has escaped its scope, but I think this is beyond the powers of the type-checker.