First, here is a self-contained version of your code (using the legacy
List.fold_left
of the standard library) for people that don't have
Core under hand and still want to try to compile your example.
module type MonadSig = sig
type 'a t
val bind : 'a t -> ('a -> 'b t) -> 'b t
val return : 'a -> 'a t
end
let sequence (module M : MonadSig) foo =
let open M in
let (>>=) = bind in
List.fold_left (fun acc x ->
acc >>= fun acc' ->
x >>= fun x' ->
return (x' :: acc')
) (return []) foo;;
The error message that you get means (the confusing first line can
be ignored) that the M.t definition is local to the M
module, and
must not escape its scope, which it would do with what you're trying
to write.
This is because you are using first-class modules, that allow to
abstract on modules, but not to have dependent-looking types such as
the return type depends on the argument's module value, or at least
path (here M
).
Consider this example:
module type Type = sig
type t
end
let identity (module T : Type) (x : T.t) = x
This is wrong. The error messages points on (x : T.t)
and says:
Error: This pattern matches values of type T.t
but a pattern was expected which matches values of type T.t
The type constructor T.t would escape its scope
What you can do is abstract on the desired type before you abstract on the first-class module T, so that there is no escape anymore.
let identity (type a) (module T : Type with type t = a) (x : a) = x
This relies on the ability to explicitly abstract over the type variable a
. Unfortunately, this feature has not been extended to abstraction over higher-kinded variables. You currently cannot write:
let sequence (type 'a m) (module M : MonadSig with 'a t = 'a m) (foo : 'a m list) =
...
The solution is to use a functor: instead of working at value level, you work at the module level, which has a richer kind language.
module MonadOps (M : MonadSig) = struct
open M
let (>>=) = bind
let sequence foo =
List.fold_left (fun acc x ->
acc >>= fun acc' ->
x >>= fun x' ->
return (x' :: acc')
) (return []) foo;;
end
Instead of having each monadic operation (sequence
, map
, etc.) abstract over the monad, you do a module-wide abstraction.