Exception vs Result

This is not really a different technique, but a solution to reduce boilerplate due to type coercion (and make it less painful).

type a = [`foo]
type b = [`bar]
type c = [`baz]

let (let*) = Result.bind
let return x = Ok x

let foo x : (_, a) result = return x
let bar x : (_, b) result = return x
let baz x : (_, c) result = return x

let f x =
  (* define a cast function to make coercion less painful *)
  let cast x = (x :> (_, [a | b]) result) in
  let* x = cast (foo x) in
  let* x = cast (bar x) in
  return x
;;
val f : 'a -> ('a, [ `bar | `foo ]) result = <fun>

let g x =
  let cast x = (x :> (_, [b | c]) result) in
  let* x = cast (bar x) in
  let* x = cast (baz x) in
  return x
;;
val g : 'a -> ('a, [ `bar | `baz ]) result = <fun>

(* or you can use a local open to define local type aliases *)
let h x =
  let open struct
    type err = [a | b | c]
    type 'a t = ('a, err) result
  end in  
  let* x = (f x :> _ t) in
  let* x = (g x :> _ t) in
  return x
;;
val h : 'a -> ('a, [ `bar | `baz | `foo ]) result = <fun>

Edit: More explicit, and I believe cleaner, way to use local open to define local type aliases.