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
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
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.
Purists will hate this, but I commonly use exceptions to emulate the “return” statement from the lesser languages.
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.
…
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.
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.
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.
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!).