Idiomatic let*, Result.bind and Lwt.bind

is that idiomatic Ocaml?

let to_let_or_not_to_let () : (unit,string) result Lwt.t =
  let ( let* ) (r : ('a,'b) result) (f : 'a -> ('c, 'b) result Lwt.t) : ('c,'b) result Lwt.t =
     match r with 
    | Ok r         -> f r
    | Error _ as e -> Lwt.return e in
  let* _ : string = Ok "string" in
  let%lwt s = Lwt.return (Ok "another") in
  let* _ : string = s in
  let* _ = Error "string" in
  Lwt.return (Ok ())

let outer () = 
  to_let_or_not_to_let ()
  |> Lwt_main.run

(required e.g. here seppo/ap.ml at e6a1942533f604e863d8588f05cea7bfa320fecc - seppo - Codeberg.org)

Is that the way to do it? Should the let* have another name?

I know very little about Lwt, but there’s Lwt_result which might already offer what you’re looking for.

2 Likes

I don’t use the syntax extension (the let%lwt) so I’m not sure what’s idiomatic for this use case. I don’t know if there are established guidelines for mixing the syntax extension and the binding operators. It’s definitely ok for the compiler, but Idk what the human reviewers who have encountered this sort of code a lot think about it.

That being said, in tezos we have some mixed lwt, result, and lwt+result monads all working side-by-side. We do this with multiple operators:

  module Lwt_result_syntax : sig
    val ( let* ) :
      ('a, 'e) result Lwt.t ->
      ('a -> ('b, 'e) result Lwt.t) ->
      ('b, 'e) result Lwt.t
    val ( let*! ) : 'a Lwt.t -> ('a -> 'b Lwt.t) -> 'b Lwt.t
    val ( let*? ) : ('a, 'e) result -> ('a -> ('b, 'e) result Lwt.t) -> ('b, 'e) result Lwt.t
    (* some other helpers *)
  end

Essentially we have the three binders which corresponds to the three different monads that you could be binding from: let* when binding from the full combined monad, let*? (where the ? represents the uncertainty of errors) when binding from a result, and let*! (where the ! represents the compulsory waiting for the resolution of the promise) when binding from an Lwt promise.

This would give you

let to_let_or_not_to_let () : (unit,string) result Lwt.t =
  let open Lwt_result_syntax in
  let*? _ : string = Ok "string" in
  let*! s = Lwt.return (Ok "another") in
  let*? _ : string = s in
  let*? _ = Error "string" in
  return_unit

(That’s a literal translation of your example; what you’d actually do is use let* _ = Lwt.return (Ok "another") in instead of the !-? sequence in the middle.)

We also have syntax modules for result-only blocks and lwt-only blocks. And even for lwt-option and option-only blocks.

You can check the whole interface and implementation which uses Lwt_result.

4 Likes

how can I write the type of r?

using your tips I arrived at the convenient let*% with

let ( let*% ) r f : ('b,'e) Lwt_result.t =
    match r with
    | Error _ as e -> Lwt.return e
    | Ok v         -> f v

lsp tells me ('a,'e) result but the compiler doesn’t accept it for line 101 in seppo/ap.ml at 20f02f88ce348f4de1bb3c91943904dd8d68e411 - seppo - Codeberg.org