Hello! I’m cleaning up the rough edges of how I’ll be using OCaml starting this year. The situation below isn’t like anything I’ve seen around here, so I thought it could be worth a post.
What I’m trying to do: I have an Intf.ml
in my library, containing common module signatures like Printable
and Jsonable
. With this scheme, I’m explicit about implementing those conventions and it avoids duplicating those declarations and doc-comments. In the case of Jsonable
however, the to_json
and of_json
functions are usually the same, so I’d like to provide that implementation, but its implementation depends on the modules’ specific to_yojson
and of_yojson
:
(* intf.ml *)
open! Base
module type Printable = sig
type t
val pp : t -> string
end
module type Jsonable = sig
type t
val to_json : t -> string
val of_json : string -> (t, [> `JsonError of string ]) Result.t
val to_yojson : t -> Yojson.Safe.json
val of_yojson : Yojson.Safe.json -> (t, string) Result.t
end
module Json = struct
let to_json to_yojson a = a |> to_yojson |> Yojson.Safe.to_string
let of_json of_yojson s =
try
match s |> Yojson.Safe.from_string |> of_yojson with
| Ok v -> Ok v
| Error e -> Error (`JsonError e)
with
| Yojson.Json_error e -> Error (`JsonError e)
;;
end
(* stuff.mli *)
type t
include Intf.Jsonable with type t := t
include Intf.Printable with type t := t
val of_int : int -> t
(* stuff.ml *)
type t = int [@@deriving yojson] (* Adds of_yojson and to_yojson *)
let to_json = Intf.Json.to_json to_yojson
let of_json = Intf.Json.of_json of_yojson
let pp s = Int.to_string s
let of_int i = i
Currying to_json
and of_json
is a very usable solution, but I’m disappointed with two aspects:
-
Is there a way around creating that second
Json
module in the namespace for stashing those two functions? I see nothing obvious. -
I wish I could “include” something in
stuff.ml
t avoid declaring those two bindings entirely. I looked into learning about functors for this (especially with flambda promising to reduce/eliminate their run-time cost), but they seem to work backwards from what I’m trying to achieve: I want my various modules likeStuff
to implement several interfaces as needed, some of which may include some helpful implementation bits. Functors would have to be used by the users of my modules to possibly achieve this, but I actually want to provide them withStuff
ready to use.
Am I missing something, or have I already reached the simplest solution with these compromises?