thanks all, I tried all suggestions, adding the simple code for completeness
2 clarifications:
- I am using ocamlformat with
conventional formatting, I tried it with default and compact but I still get indentations
- List.length was an arbitrary
if check to showcase nesting
Setup
open Base
module Error_service = struct
let log m s =
Stdio.print_endline m;
Stdio.print_endline s
end
module My_service = struct
type a_error = A1 | A2
let string_of_a_error = function A1 -> "a1" | A2 -> "a2"
let get_a () : (int, a_error) Result.t Lwt.t = Lwt.return (Ok 5)
type b_error = B1
let string_of_b_error = function B1 -> "b1"
let get_b x : (int list, b_error) Result.t Lwt.t = Lwt.return (Ok [ 2 * x ])
type c_error = C1 | C2
let string_of_c_error = function C1 -> "c1" | C2 -> "c2"
let get_c x : (int, c_error) Result.t Lwt.t = Lwt.return (Ok (List.length x))
end
original version - lots of nesting
let nested_fn () : (int, unit) Result.t Lwt.t =
let open Lwt.Syntax in
let* a = My_service.get_a () in
match a with
| Error err ->
Error_service.log "error with a" (My_service.string_of_a_error err);
Lwt.return (Error ())
| Ok value -> (
let* b = My_service.get_b value in
match b with
| Error err ->
Error_service.log "error with b" (My_service.string_of_b_error err);
Lwt.return (Error ())
| Ok value -> (
if List.length value > 5 then Lwt.return (Error ())
else
let+ c = My_service.get_c value in
match c with
| Error err ->
Error_service.log "error with c"
(My_service.string_of_c_error err);
Error ()
| Ok value -> Ok (value * 5) ) )
using Lwt_result.Syntax
let or_log_error m to_string ctx =
Lwt_result.map_err (fun e -> Error_service.log m (to_string e)) ctx
let pipes_fn () : (int, unit) Result.t Lwt.t =
let open Lwt_result.Syntax in
let* a =
My_service.get_a ()
|> or_log_error "error with a" My_service.string_of_a_error
in
let* b =
My_service.get_b a
|> or_log_error "error with b" My_service.string_of_b_error
in
if List.length b > 5 then Lwt.return_error ()
else
let* c =
My_service.get_c b
|> or_log_error "error with c" My_service.string_of_c_error
in
Lwt.return_ok (c * 5)
same, but without pipes and handling the error inline
let map_err t f = Lwt_result.map_err f t
let no_pipes_fn () : (int, unit) Result.t Lwt.t =
let open Lwt_result.Syntax in
let a = My_service.get_a () in
let* a =
map_err a (fun e ->
Error_service.log "error with a" (My_service.string_of_a_error e))
in
let b = My_service.get_b a in
let* b =
map_err b (fun e ->
Error_service.log "error with b" (My_service.string_of_b_error e))
in
if List.length b > 5 then Lwt.return_error ()
else
let c = My_service.get_c b in
let* c =
map_err c (fun e ->
Error_service.log "error with c" (My_service.string_of_c_error e))
in
Lwt.return_ok (c * 5)
infix operators - with ocamlformat I still got nested code, but even though I find infix operators less readable compared to syntax extensions - the use of function made it easy to follow
let log_error e m to_string =
Error_service.log m (to_string e);
Error ()
let infix_fn () : (int, unit) Result.t Lwt.t =
let open Lwt.Infix in
My_service.get_a () >>= function
| Error e ->
Lwt.return (log_error e "error with a" My_service.string_of_a_error)
| Ok a -> (
My_service.get_b a >>= function
| Error e ->
Lwt.return (log_error e "error with b" My_service.string_of_b_error)
| Ok b -> (
if List.length b > 5 then Lwt.return_error ()
else
My_service.get_c b >|= function
| Error e -> log_error e "error with c" My_service.string_of_c_error
| Ok c -> Ok (c * 5) ) )
summary of different types solutions:
- map all monads to one type (
'a Lwt.t -> ('a, ..) Result.t Lwt.t)
- define different operators to different monads
- use
Result.map and Lwt_result.map, but define a common to the codebase naming convention (and argument order, I for example have a strong preference to JaneStreet’s t-first functions)