For what it’s worth, it turns out that GADTs were counter-productive to reach my ultimate goal. I could never “curry” with them, with yet another a b not included in 'a b
kind of error in the use of signature I
. A plain private variant turned out to work with my cleaned up structure. The relevant bits:
module Buf = struct
(* types bigstring_buf, buffer_buf, bytes_buf, string_buf *)
type t =
| Bigstring_buf of bigstring_buf
| Buffer_buf of buffer_buf
| Bytes_buf of bytes_buf
| String_buf of string_buf
module type S = sig
val length : unit -> int
end
module Make_bytes (I : sig
val b : bytes_buf
end) : S = struct
let length () = Bytes.length I.b.data
end
(* Make_bigstring(), Make_buffer(), Make_string() ... *)
let get_module bt =
match bt with
| Bytes_buf b -> (module Make_bytes (struct let b = b end) : S)
(* ... *)
end
module Some_codec = struct
let decode src dst =
let module In = (val Buf.get_module src) in
let module Out = (val Buf.get_module dst) in
(* Use In and Out ... *)
dst
let test str = str |> Buf.of_string |> decode (Buf.new_buffer 4096)
(* val test : string -> Buf.t *)
end
The end result is that not only can Some_codec
abstract away the input and output buffer implementations, but there’s also no run-time cost in doing so after the initial calls to Buf.get_module
.