Pattern matching syntactic sugar

Hi, OCaml beginner here. I’m coming from Rust, which has some syntactic sugar/quality of life features around pattern matching and errors. I’m curious what the idiomatic equivalents are for OCaml?

Early return

Basically, if you have an error, you can return early:

let file = match fs::read("hello.txt") {
    Ok(file) => file,
    Err(err) => return Err(err)
};

let file = fs::read("hello.txt")?;

If I understand this thread correctly, there’s no easy way to do early return in OCaml. I suppose the simplest equivalent would be to use some combination of Result.bind and |>? Or let*?

As an aside, this type checks because return creates the type !, i.e. the Never type, which indicates a computation that doesn’t finish.

If let

This is nice for situations where you need to match on a single pattern:

if let Ok(file) = fs::read("hello.txt") {
  println!("opened file");
}
if let Ok(file) = fs::read("hello.txt") {
  println!("opened file");
} else {
  println!("failed!");
}

While let

Equivalent of if let for loops.

while let Some(token) = lexer.next() {
  process_token(token);
}

Let…else

Fairly new feature that lets you pattern match or do something that diverges, i.e. throw an error:

let Expr::Int(i) = parse() else {
  return Err(anyhow!("expected an integer"))
}

todo!

If there’s a branch that you haven’t implemented but you need the code to type check, you can add todo!() and it’ll compile (but panic at runtime):

match parse() {
  Expr::Int(i) => handle_int(i),
  _ => todo!(),
}

Thanks in advance for your help!

Generally speaking, none of these exist in OCaml. Some of them have been considered over the years, but did not gather enough consensus to be merged.

“Early return” can be simulated using exceptions or monadic operators, but there isn’t anything built-in.

Regarding “if let”: see the discussion add a `if let` construct to the language by c-cube · Pull Request #194 · ocaml/ocaml · GitHub.

Cheers,
Nicolas

This one does have an analog: assert false (or Stdlib.exit).

Cheers,
Nicolas

Unless I am missing something, let p = e1 else e2 can be written in OCaml:

match e with p -> e1 | _ -> e2

Cheers,
Nicolas

Does assert false have a type that can be unified with any other type? That’s what makes todo! special, you can do something like:

let i = match parse() {
  Expr::Int(i) => i,
  _ => todo!()
}

And it will type check.

For let...else, the else branch is guaranteed to diverge, i.e. return, panic, or some other control flow. You can read it as “match this pattern or early exit”

Yes.

Cheers,
Nicolas

1 Like

I’d generally do failwith "todo" over assert false. Otherwise you should probably do assert false (* TODO *) to differentiate from an assert false which is meant to fill in an impossible case.

3 Likes

let p = e1 else e2 is an abomination. A truly grotesque and disgusting abuse of language.

1 Like

I usually use the bindings operators when I need to chain Result in successives calls:

let ( let* ) = Result.bind in

let* data = read file in
let* … in
Ok ()

You do not need early return for the first one. Yes you can use let*, but its not even needed.

The following is fine :

match open_in "hello.txt" with
| Error err -> Err err
| Ok file ->
f file

If we imagine an early_return function in ocaml, then the above is equivalent to the bellow.

let file = 
  match open_in "hello.txt" with
  | Error err -> early_return (Err err)
  | Ok file -> file
in
f file

Because the match syntax does not have brackets, its okay-ish to not indent the last case, which makes it even clearer that you intend this as a sort of a let. Ocamlformat might not agree with that theory.

Thanks for the suggestion! To be frank, I’d prefer to keep using ocamlformat than to have this slightly less indented code. Besides, the indentation does seem to serve a visual purpose of indicating the branch taken. Otherwise it’s possible to accidentally read that code as unconditional.

Plus in more nested situations, this seems rather confusing, e.g.:

match open_in "hello.txt" with
| Error err -> Err err
| Ok file ->
match f file with
| Error err -> Error err
| Ok result ->
foo result;

open_in "goodbye.txt" (* is this nested or not? *)

Ocamlformat has an option to format like I shown but only in certain cases (match whose last branch is a match is one).

Of course you can use my trick in still use nesting.

1 Like