I’m currently learning using ppxlib to write ppx deriver, and I found this tutorial. Here’s an example of automatically generating a stringify
function:
let rec expr_of_type typ =
let loc = typ.ptyp_loc in
match typ with
| [%type: int] -> [%expr string_of_int]
| [%type: string] -> [%expr fun i -> i]
| [%type: bool] -> [%expr string_of_bool]
| [%type: float] -> [%expr string_of_float]
| [%type: [%t? t] list] ->
[%expr
fun lst ->
"["
^ List.fold_left
(fun acc s -> acc ^ [%e expr_of_type t] s ^ ";")
"" lst
^ "]"]
| _ ->
Location.raise_errorf ~loc "No support for this type: %s"
(string_of_core_type typ)
let generate_impl ~ctxt (_rec_flag, type_decls) =
let loc = Expansion_context.Deriver.derived_item_loc ctxt in
List.map
(fun typ_decl ->
match typ_decl with
| { ptype_kind = Ptype_abstract; ptype_manifest; _ } -> (
match ptype_manifest with
| Some t ->
let stringify = expr_of_type t in
let func_name =
if typ_decl.ptype_name.txt = "t" then { loc; txt = "stringify" }
else { loc; txt = typ_decl.ptype_name.txt ^ "_stringify" }
in
[%stri let [%p Pat.var func_name] = [%e stringify]]
| None ->
Location.raise_errorf ~loc "Cannot derive anything for this type"
)
| _ -> Location.raise_errorf ~loc "Cannot derive anything for this type")
type_decls
This deriver can be used like:
type i_list = int list [@@deriving stringify]
type b_list = bool list [@@deriving stringify]
type i_list_list = int list list [@@deriving stringify]
let () =
let i_lst = [ 1; 2; 3 ] in
let b_lst = [ true; false; true ] in
let i_lst_lst = [ [ 1; 2; 3 ]; [ 4; 5; 6 ] ] in
print_endline (i_list_stringify i_lst);
print_endline (b_list_stringify b_lst);
print_endline (i_list_list_stringify i_lst_lst)
However, it does not support types with type alias, for example.
type i_list = int list
type i_list_list = i_list list [@@deriving stringify] (* does not work *)
I checked the documentation for OCaml AST used in ppxlib, and found that type aliasis can be matched by adding a pattern-matching branch in function expr_of_type typ
:
| {ptyp_desc = Ptyp_constr ({ txt = lid; _}, lst); _} ->
Here, lid
would be the name of the constructor, and lst
is the list of “parameter” (honestly I don’t know the term, since the documentation didn’t explain this). Basically, if we have int list
, lid
would be "list"
, and lst
would be a list containing a core_type
of int
. In the case of type i_list_list = i_list list
, lst
will be a list containing a core_type
of i_list
.
However, I cannot get the definition (int list
) of i_list
from the core_type
of it, as i_list
cannot be matched to patterns like [%type: [%t? t] list]
, and I therefore cannot recursively find the base definition and implement the stringify
function for types with type alias.
To get the definition, one can try to get the corresponding type_declaration
from the core_type
of the type alias, then get the field ptype_manifest
. Notice that this can be done recursively, so it will be fine even if the definition contains type aliases.
I wonder if there are any ways to do so. Since ppxes like ppx_deriving.show
have similar functionalities, I checked the source code, but wasn’t able to fully understand it. Here might be a related section, hopefully it helps: ppx_deriving/src_plugins/show/ppx_deriving_show.ml at 1268719e7117a80bcfe89c91a2c624a3eb171c8d · ocaml-ppx/ppx_deriving · GitHub
I have also found that the documentation of ppxlib is not that complete and does not include a clear explanation for each APIs. Are there any good resources?