The Factory
module type can be factored out e.g.
module type Factory = sig
type wrapped = private ..
type t
type wrapped += Wrap : t -> wrapped
type creator
val createItem : creator
val wrap : t -> wrapped
end
module Wrapper : sig
type t = private ..
module type Factory = Factory with type wrapped := t
(* ... *)
But it’s not possible to integrate the substitution wrapped := t
directly into the module type (module Factory with type creator = 'a and type t = 'b)
like ... and type wrapped := t
. Currently OCaml only permits equality constraints in first-class module types, not destructive substitutions (:=
), so a intermediate module type is needed, or the wrapped
type will remain in the modules produced by createFactory
, although it shouldn’t be a problem if those types will all be equal to Wrapper.t
.
Actually, you can work-around this restriction by using a functor:
module Factory (W : sig type t = private .. end)= struct
module type S = sig
type t
type W.t += Wrap : t -> W.t
type creator
val createItem : creator
val wrap : t -> W.t
end
end
module Wrapper : sig
module T : sig
type t = private ..
end
type t = T.t
type ('a, 'b) aux = (module Factory (T).S with type creator = 'a and type t = 'b)
val createFactory : 'a -> ('a, _) aux
(* ... *)
If you mean the type manifest = private ..
, it’s just to allow to extend the type t
using the alias wrapped
(type wrapped += Wrap : t -> wrapped
), and is only used because of the name collision (type t
in the Factory
vs. type t
in the outer Wrapper
module) to be able to express the type of the extension constructor Wrap
. E.g. it’s not needed if you use a functor as above.
No, unfortunately this is the primary problem here: It’s not possible to capture the “return type of any function type” in general by using only unification. E.g. _ -> 'rtype
only works for functions with one unlabelled argument, one:_ -> two:_ -> 'rtype
– for functions with two labelled arguments etc. And a first-class higher-kinded type 'rtype 'creator
also cannot be used as first, they are not supported in OCaml and, more importantly, the restricted version of first-class higher-kinded types that is decidable and used e.g. in Haskell, relies on kinding restriction and so the type constructor ->
that has kind * -> * -> *
still can not be unified with a constructor 'creator
with kind * -> *
(the unification of such type 'rtype 'creator
with some other type (e.g. int -> int
) is an example of second-order unification problem, which is undecidable in general, even here we can see several solutions: 'rtyp = int
, 'r creator = int -> 'r
and 'rtyp = int -> int
, 'r creator = 'r
). So it’s not possible to faithfully represent the desirable type of function createFactory
without resorting to some workarounds. The typical workaround for OCaml is using module language, where the user provides the solution to the unification problem explicitly be defining parametric types, e.g.
module Create_factory (M : sig type t type 'a creator val creator : t creator end) : sig
include module type of M
val wrap : t -> W.t
end
but then the type annotation overhead is even bigger (not only the return type, but the type of the entire function has to be given). I suggested a work-around with the annotation on the return type only. It’s also possible to use continuation-passing style as a work-around e.g.:
module Wrapper : sig
module T : sig
type t = private ..
end
type t = T.t
type ('a, 'b) aux = (module Factory (T).S with type creator = 'a and type t = 'b)
val createFactory : (('r -> 'r) -> 'a) -> ('a, 'r) aux
end = struct
module T = struct
type t = ..
end
type t = T.t = ..
type ('a, 'b) aux = (module Factory (T).S with type creator = 'a and type t = 'b)
let createFactory (type a r) (create : (r -> r) -> a) : (a, r) aux =
(module struct
type t += Wrap : r -> t
type t = r
type creator = a
let wrap x = Wrap x
let createItem = create (fun x -> x)
end)
end
module Int_factory = (val Wrapper.createFactory (fun k ~one ~two -> k @@ one + two))
module String_factory = (val Wrapper.createFactory (fun k ~one ~two ~three -> k @@ one ^ two ^ three))
Now there’s no need in type annotation, but the functions passed to createFactory
have to use the continuation instead of directly returning the result.
So if you fix either the return type or the “shape” of the creator function (say, only one parameter that can be a tuple, triple, record etc. e.g. (1, 2)
) , then no workaround for higher-kinded types is needed (and so no annotations on the user side). But I think you’ll still need locally abstract types to be able to include the type of function parameter into the resulting module e.g. type param = a
, where createItem
has type a -> int
(when defining the createFactory
function).