One thing worth keeping in mind is that the compiler will use type inference to allow unqualified use of constructors. E.g., in a fresh top level we can write
# List.partition_map (fun x -> Left x) [1;2;3;4];;
- : int list * 'a list = ([1; 2; 3; 4], [])
Using the unqualified Left
because the type of List.partition_map
tells the compiler that the Left
is coming from Either
. An upshot is you can use a sprinkling of type annotations to avoid needing an open or qualified use. So
# let l : _ Either.t list = [Left 1; Right "Foo"];;
val l : (int, string) Either.t list = [Either.Left 1; Either.Right "Foo"]
I often use scoped open sugar too, and may well construct that list as
# Either.[Left 1; Right "foo"];;
This is all just to say that, IMO, existing language constructs make it possible to use constructors in a quite light weight way, with the benefits of qualified use but many times having it appear unqualified.
That all said, afaict, @liate’s suggestions for type aliases does indeed bring the type constructor in scope in the desired way:
# type ('a, 'b) either = ('a, 'b) Either.t = Left of 'a | Right of 'b;;
type ('a, 'b) either = ('a, 'b) Either.t = Left of 'a | Right of 'b
# Left 1 = Either.Left 1;;
- : bool = true
And if we add a type constraint we can use the sealed open too:
# open (Either : sig type ('a, 'b) t = Left of 'a | Right of 'b end with type ('a, 'b) t = ('a, 'b) Either.t);;
type ('a, 'b) t = ('a, 'b) Either.t = Left of 'a | Right of 'b
# Left 1 = Either.Left 1;;
- : bool = true
If we wanted this open regularly without so much ceremony, you could name the module type and then do the sealed open like this:
# module type EitherType = sig type ('a, 'b) t = Left of 'a | Right of 'b end with type ('a, 'b) t = ('a, 'b) Either.t;;
module type EitherType =
sig type ('a, 'b) t = ('a, 'b) Either.t = Left of 'a | Right of 'b end
# open (Either : EitherType);;
type ('a, 'b) t = ('a, 'b) Either.t = Left of 'a | Right of 'b
# Left 1 = Either.Left 1;;
- : bool = true