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.