Why type is not matching

For example I have the code that wants to abstract the nature of buffer types, lets take just string and bytes for the simplicity:

open Core

module type Byteable = sig
   type t
   val length: t -> int
   val to_bytes: t -> bytes
end

module StringB : sig
    type t
    val length: t -> int
    val to_bytes: t -> bytes
end

module BytesB: sig
   type t
   val length: t -> int
   val to_bytes: t -> bytes
end

module StringB : Byteable = struct
   include String
   let to_bytes = Bytes.unsafe_of_string_promise_no_mutation
end

module BytesB: Byteable = struct
   include Bytes
   let to_bytes a = a
end

Then if I define a functor, something like:

(* this one just to clarify it always accepts bytes *)
val call_some_function : bytes -> unit

module Make_stuff (M : Byteable) = struct
    let do_some_weird_stuff ~data =
        let databytes = M.to_bytes data in
        call_some_function databytes
end

module MyBytes = Make_stuff (BytesB)
module MyString = Make_stuff (StringB)

With just this it compiles fine and well. But if I actually try to use the resulting modules function do_some_weird_stuff it requires BytesB.t type instead of bytes or StringB.t type instead of string.
For example if I call something like:

let cool_string = "something" in
MyString.do_some_weird_stuff ~data:cool_string

will produce a compile error:

this expression has type string but an expression was expected of type StringB.t

Why does it happen?

One problem is that the signature

module StringB : sig
    type t
    val length: t -> int
    val to_bytes: t -> bytes
end

make the type StringB.t abstract. Thus outside of the module StringB, the only information known about t is that, it can be consumed by StringB.length or StringB.to_byte. In particular, there is no way to construct a value of type t from types outside of the module.

If you want to expose the fact that StringB.t = string, you need to state this equality in the signature explicitly:

module StringB : sig
    type t = string
    val length: t -> int
    val to_bytes: t -> bytes
end

Well, I tried to do

module StringB : sig
    type t = string
    val length: t -> int
    val to_bytes: t -> bytes
end

but it complains that this doesn’t match to the module type:

The implementation does not match the interface ....:
In module StringB:
Type declarations do not match:
  type t = StringB.t
is not included in 
  type t = string

What does the implementation look like?

It is already in the post:

module StringB : Byteable = struct
   include String
   let to_bytes = Bytes.unsafe_of_string_promise_no_mutation
end

module BytesB: Byteable = struct
   include Bytes
   let to_bytes a = a
end

So you have the same problem with Byteable which is not an usable module type by itself, you most probably want Byteable with type t = string (and Byteable with type t = concrete_type for other implementations).

Thanks, this did the job. But the original idea was to derive the type from the String or Bytes modules (string and bytes types), is this possible somehow?

In the implementation, it is possible to remove the signature constraint:

module StringB = struct
   include String
   let to_bytes = Bytes.unsafe_of_string_promise_no_mutation
end

and let the compiler infers the module type by itself.

2 Likes