Another question about using ppx with dune/jbuilder

I can use ppx_deriving make successfully in utop. I’m trying to figure out how to use it with dune (as a first step to using deriving in other ways, along with other ppx processors). What should I have in the jbuilder file for this purpose? (I have read anything I can find that seems relevant in the jbuilder/dune docs or in the ppx_deriving README, but I have not yet figured this out. A tutorial on this sort of thing would be very welcome.)

foo.ml:

type t = {a : int; b : float} [@@deriving make]

let () =
  (* let make_t x y = {a=x; b=y} in *)
  let f y x = make_t x y in
  let foo1 = f 2.1 3 in
  Printf.printf "%f %d\n" foo1.b foo1.a

The best hypothesis I have about what should go in jbuild is something like this:

(jbuild_version 1)

(executables
 ((names (foo))
  (public_names (foo))
  (package bar)
  (preprocess (pps (ppx_deriving.std)))
  (libraries (core ppx_deriving.runtime))
  (modes (native))))

However, compiling with jbuilder build @install produces:

...
File "foo.ml", line 6, characters 14-20:
Error: Unbound value make_t
Hint: Did you mean make?

If I uncomment the definition of make_t in foo.ml, the executable builds and runs with expected output.

Isn’t this purely due to ppx_deriving calling the deriver_type functions just deriver it the type is called t as a convenience? Like show_foobar on type foobar [@@deriving show] but show on type t [@@deriving show]?

The message seems to point you to the fact that there is a make function.

You’re right! Thanks. It didn’t occur to me that the behavior would be different for type t vs. other names. (Not very obvious!) I thought that the error message was referencing some other make function that was unrelated.

This is due to the OCaml convention to call the “main” type of a module t. For example,

module List = struct
  type 'a t = Nil | Cons of 'a * 'a t
    [@@deriving eq, compare, hash, sexp]

  let empty = Nil

  let rec member xs x =
  ...
end

The convention makes for very readable code:

let l1 : List.t = List.empty in
let l2 : List.t = List.(cons 2 empty) in
if List.equal l1 l2 then
  List.hash l1
else
  List.hash l2

Compare that to

if List.equal_t l1 l2 then
  List.hash_t l1
else
  List.hash_t l2

It should be fairly uncontroversial that the _t suffix is both ugly and pointless.

1 Like

Yes, thanks @smolkaj. Nothing I’d seen hinted that deriving make worked this way.

EDIT: Ah, I found it. Earlier in the ppx_deriving README:

By default, the functions generated by a plugin for a type foo are called fn_foo or foo_fn. However, if the type is called type t, the function will be named foo. The defaults can be overridden by an affix = true|false plugin option.

I believe the ppx_deriving.runtime does not have to declared as a library. In fact, I found the jbuild file below to be working. The library still is linked into the binary when required.

(jbuild_version 1)

(executables
 ((names (foo))
  (public_names (foo))
  (package bar)
  (preprocess (pps (ppx_deriving.std)))
  (modes (native))))