I have an issue - I haven’t found an easy way to structure code in a simple, readable way.
I’m working with APIs that return Lwt results, and I need to handle both the Ok and Error cases, so I can’t just map them.
I also have some conditions (if/else) that can’t be included in the match ‘when’
In an imperative language I would use early exits, so the code would look like (this is a simplified version):
const a = await myService.getA();
if(!a.isOk) {
errorService.log('error with a', a.err);
return Error('a');
}
const b = await myService.getB(a.value);
if(!b.isOk) {
errorService.log('error with b', b.err);
return Error('b');
}
if(b.length > 5) {
return Error('z');
}
const c = await myService.getC(b.value);
if(!c.isOk) {
errorService.log('error with c', c.err);
return Error('c');
}
return c.value * 5;
but the same in OCaml gets very nested, with many brackets, so it gets difficult to follow (and that’s a simple, contrived example):
let* a = MyService.get_a () in
match a with
| Error err ->
ErrorService.log "error with a" err
Lwt.return (Error A)
| Ok value -> (
let* b = MyService.get_b value in
(match b with
| Error err ->
ErrorService.log "error with b" err
Lwt.return (Error B)
| Ok value ->
if List.length value > 5
then Error Z
else (
let* c = MyService.get_c value in
(match c with
| Error err ->
ErrorService.log "error with c" err
Lwt.return (Error C)
| Ok value -> value * 5)
)
)
)
note: I don’t want to move each handling to a separate function, because it then gets even more annoying to read - you have to jump to separate definitions instead of reading the code top-down
How do you deal with early exits, deeply nested code and handling of both Ok and Result in OCaml?