How can sexplib be used with functor types like Map?

680 Views Asked by At

Sexplib's syntax extension makes serialization and deserialization of arbitrary user-defined data structures easy in OCaml. It is generally done by adding a with sexp annotation to the end of a type definition:

type a = A of int | B of float with sexp

This does not seem to generalize directly to functor-based types, nor is it clear how the Sexplib standard type converters can capture even the standard functors.

So far I have worked around this by flattening a specific Map type instance (e.g. int Map.Make(String).t) to a list before serialization, and vice versa, but surely this hasn't been completely overlooked by the generally ambitious authors of Sexplib/Jane Street Core. I also notice that older versions of Batteries mix in custom sexp serialization to their major modules like [Bat]Map, but that this has been removed for some time.

How are Maps or other complex functor types commonly used with Sexplib serialization?

1

There are 1 best solutions below

0
On

One way is to define a new functor that takes in the additional information needed to serialize the data. Here's a complete implementation I have used in the past with Batteries. Note I also preferred the exceptionless and labeled version of Map, so I've opened those, but you could of course remove that.

module type SEXPABLE = sig
  type t
  val sexp_of_t : t -> Sexplib.Sexp.t
  val t_of_sexp : Sexplib.Sexp.t -> t
end

module Map = struct

  module type S = sig
    include BatMap.S
    include module type of Labels
    include module type of Exceptionless
    val sexp_of_t : ('a -> Sexplib.Sexp.t) -> 'a t -> Sexplib.Sexp.t
    val t_of_sexp : (Sexplib.Sexp.t -> 'a) -> Sexplib.Sexp.t -> 'a t
  end

  module Make (Ord : BatInterfaces.OrderedType)
              (Sexpable : SEXPABLE with type t = Ord.t)
              : S with type key = Ord.t = struct
    include BatMap.Make(Ord)
    include Labels
    include Exceptionless

    open Sexplib.Sexp
    open Sexplib.Conv

    let sexp_of_t sexp_of_data t =
      let f ~key ~data ans = List [Sexpable.sexp_of_t key; sexp_of_data data] :: ans in
      List (fold ~f ~init:[] t)

    let t_of_sexp data_of_sexp sexp = match sexp with
      | Atom _ -> of_sexp_error "Map.Make(...).t_of_sexp: list needed" sexp
      | List l ->
          let f ans = function
            | List [key_sexp; data_sexp] ->
                let key = Sexpable.t_of_sexp key_sexp in
                let data = data_of_sexp data_sexp in
                add ~key ~data ans
            | List _ | Atom _ ->
                of_sexp_error "Map.Make(...).t_of_sexp: 2-tuple list needed" sexp
          in
          List.fold_left ~f ~init:empty l
  end
end

If I recall correctly, Batteries removed such features to reduce dependencies on additional libraries. Your other option is to use Core, which has these functions out-of-the-box.