Early return in OCaml


#1

I just realized that OCaml syntax is kind of uniquely poised to support early return among functional languages. Here’s an example:

let suffix length string =
  if length < 0 then "" else
  let string_length = String.length string in
  if string_length < length then "" else

  String.sub string (string_length - length) length

Yeah, it’s really bad … but it’s kinda cool :slight_smile:


#2

This function is in batteries:

BatString.right str suf_len

However, corner cases may behave differently than with your function.


#3

I’d say that in Functional Programming style one should prefer pattern matching to early returns. I believe PM is generally easier to read and you can get more assistance from the compiler as well, e.g. the compiler can spot some missing cases. I would write something like this:

(* Copyright 2019 Google LLC
   SPDX-License-Identifier: Apache-2.0 *)
(* it's a shame OCaml doesn't have the Ordering type
   and uses ints for comparisons. *)
type ordering = Lt | Eq | Gt

let compare x y = if x < y then Lt else if x > y then Gt else Eq

let suffix ~len ~str =
  let l = max 0 len in
  let n = String.length str in
  match compare l n with
  | Lt -> String.sub str (n - l) l
  | Eq | Gt -> str

#4

Is it really more readable than this?

if l < n then
  String.sub str (n-l) l
else
  str

Pattern matching would be more useful if the variant was expected to be extended some time in the future, but I totally don’t expect ordering to ever be changed.

For an experienced programmer, a < b is already akin to syntactic sugar that expands into your match compare a b with Lt -> true | Eq | Gt -> false. There’s no need to be so explicit.


#5

Purists will hate this, but I commonly use exceptions to emulate the “return” statement from the lesser languages.

:sweat_smile:

This little fellow makes things look less nasty:

let label (type u) (f: (u -> _) -> u) : u =
  let exception R of u in
  try f (fun x -> raise (R x)) with R u -> u

Usage example:

let f () : int =
  label begin fun return ->

    ...
    
    if cond1 then
      return 1;
    
    ...
    
    if cond2 then
      return 2;
    
    ...
      
    3 (* result *)
  end

Almost as good as in C, buahahahaha. :rofl:

P.S. I wish OCaml had a built-in “return” construct, with a more efficient implementation, but I wouldn’t dare sending a PR on this (even though it’s probably trivial to implement). A “goto” statement would also be welcome (for modelling state machines and such, especially inside generated code), but I guess that it’s even more of a blasphemy.


#6

What do you think of this instead:

let f () =
  ...
  if cond1 then 1 else
  ...
  if cond2 then 2 else
  ...
  3

#7

Base has a convinient way to perform an early return: Base.With_return. The implementation is similar to @murmour’s example, i.e., it uses local exceptions.


#8

The scenario I was thinking of with my ‘trick’ was really function parameter validation. That’s often held up as a criticism of FP languages–that you can’t do low-overhead validation and keep a ‘flatter’ structure for the overall function. But yeah, for short-circuiting you certainly need an exception-based mechanism.


#9

I think it’s fine for short expressions, but for something larger seeing the structure becomes difficult. Even in your example it is somewhat “bad”, as you noted.

An alternative is to add indentation:

let f () =
  ...
  if cond1 then 1 else
    ...
    if cond2 then 2 else
      ...
      3

I use this style to make the resulting values stand out, while keeping the indentation from growing:

let f () =
  if cond1 then
    1
  else if cond2 then
    2
  else
    3

I’ve also tried control-flow monads, but found them too heavy (and slow!).