Sharing: newbie caught out by dumb mistake with promises

I had a trivial little Lwt program like this:

let read_name =
  let%lwt name = Lwt_io.(read_line stdin) in
  Lwt.return (Some name)

let timeout =
  let%lwt () = Lwt_unix.sleep 3.0 in
  Lwt.return None

let () =
  print_endline "Enter your name (but don't take too long)";
  let first_resolved = Lwt_main.run (Lwt.pick [ read_name; timeout ])
  in
  match first_resolved with
  | Some name -> Printf.printf "Hello %s\n" name
  | None -> print_endline "Too slow!"

It’s working just fine and neither read_name nor timeout need to be actual functions because, well, they are promises so everything gets deferred.

Then I add another block, rewriting read_name without the specialised let.

let _read_name =
  let name_promise = Lwt_io.(read_line stdin) in
  Lwt.bind name_promise (fun name -> Lwt.return (Some name))

I make some other tweaks, add some comments and all of a sudden, now my program consumes an extra newline: “Fred” gives an empty string and “Fred” is what you need to type to get “Hello Fred”.

I will skip over the confusion and head-scratching (of course I didn’t re-run the program until the next day at which point I wasn’t sure if it had worked properly in the first place).

My mistake? I inserted that second block of code before the read_name I was actually using. And since they weren’t actually functions but just values both were being evaluated. And _read_name also has read_line stdin so was consuming my first line of data.

Use actual functions and Lwt.pick [ read_name (); timeout () ] and all is good.

Hopefully if someone else makes the same mistake this post will have enough keywords to make it searchable.

2 Likes