Deriving show ppx and functors

Hello,

I have started to use the show PPX extension available from https://github.com/thierry-martinez/ppx_show and found it very useful.

But i’m now facing facing a problem when trying to use it in coordination with functors. I’ll try to illustrate it using a small example (which actually is a heavily trimmed down version of the code i’m working on).

Suppose i want to manipulate expressions in which constants can be of any type (not just int). I can do this by writing

(* file expr.mli *)
module type T = sig
  type value
  type t = Const of value | Var of string
  val zero: t
end
                         
module type VALUE = sig
  type t
  val from_int: int -> t
end

module Make (V: VALUE) : T with type value = V.t
(* file expr.ml *)
module type T = sig
  type value
  type t = Const of value | Var of string
  val zero: t
end

module type VALUE = sig
  type t
  val from_int: int -> t
end
                         
module Make (V: VALUE) = struct
  type value = V.t
  type t = Const of value | Var of string
  let zero = Const (V.from_int 0)
end

So far, so good. In particular, i can write (in file main.ml for ex):

module Int = struct
  type t = int
  let from_int x = x
end
           
module IntExpr = Expr.Make(Int)

let v = IntExpr.zero

Now, suppose i want to add a show function to module Expr to display (as strings) values of type Expr.t. I suppose i have to rewrite the definition of type t in the Makefunctor as

type t = Const of value | Var of string [@@deriving show]

It seems (?) i also have to add the PPX annotation in T signature (in expr.ml and expr.mli).

Doing this, i get the following error message:

File ".../expr.ml", line 1:
Error: Unbound value pp_value

Seems normal: the derived show function for values of type T.t needs to know how to show values of type Value.t.

So, i added this line to the VALUE signature in files expr.{ml,mli}:

  val pp_value: t -> string

and this line to the definition of the Int module in main.ml:

  let pp_value = string_of_int

but i still get the error:

File ".../expr.ml", line 1:
Error: Unbound value pp_value

I suspect this has to do with the fact that the PPX rewritter does not have the correct function in scope in the context of functor application. I tried to inspect the ppx-expansed code (*.pp.ml generated by dunein _builddirectory) but unfortunately it is not in human readable form :frowning:

Any help on this subject would be appreciated

Jocelyn

3 Likes

You need to make the value printer, pp_value, visible in scope at the definition of t:

open Value
(* or *)
let pp_value = Value.pp_value
type t = Const of value | Var of string [@@deriving show]

Note that you can expand the pp.ml file into source format by using ocamlopt -dsource file.pp.ml

1 Like

Another solution might be to use the original name of value in the type definition, which should cause the PPX to expect the correct code:

module type VALUE = sig
  type t [@@deriving show]

  (* ... *)
end
                         
module Make (Value: VALUE) = struct
  type t =
    | Const of Value.t (* rather than [value] *)
    | Var of string [@@deriving show]

  (* ... *)
end

@octachron
Thanks for your quick and useful answer.
I should still be missing sth, howver :confused:

(* expr.ml *)
...
module type VALUE = sig
  type t 
  val from_int: int -> t
end
                         
module Make (Value: VALUE) = struct
  open Value 
  type t = Const of Value.t | Var of string [@@deriving show]
  let zero = Const (Value.from_int 0)
end
File "examples/functor/expr.ml", line 1:
Error: Unbound value Value.pp

Ok, let’s pp to the VALUE signature:

module type VALUE = sig
  type t 
  val from_int: int -> t
  val pp: Format.formatter -> t -> unit
end

Incidentely, pp must have type Format.formatter -> t -> unit and not t -> string (as i expected).

Now, i guess i have to define the correct value of pp in the Int structure. Being unfamilar with formaters, i don’t know how to do this :frowning:

module Int = struct
  type t = int
  let from_int x = x
  let pp fmt = ???
end

@CraigFe
Thanks also for your help.
Using the original type name in the functor definitions makes things clearer, indeed. I’ve used this reformulation in my previous answer.

I don’t understand how adding [@@deriving show] to the type VALUE.t can work without adding somewhere a reference to the actual printer. It will just define a function VALUE.show, won’t it ? How is this function supposed to call the “right” code ?

Sorry if this sounds confusing. I guess i have to use the tip suggested by @octachron and inspect the PPX generated code to understand what’s going on “under the hood” …

You can derive the Int printer too:

module Int = struct
  type t = int [@@deriving show]
  ...
end

Of course !

Everything’s ok now :slight_smile:

Here’s the final code, in case s/o encounters the same problem.

Thanks !

(* expr.mli *)
module type T = sig
  type value
  type t = Const of value | Var of string [@@deriving show]
  val zero: t
end
                         
module type VALUE = sig
  type t 
  val from_int: int -> t
  val pp: Format.formatter -> t -> unit
end

module Make (V: VALUE) : T with type value = V.t
(* expr.ml *)
module type T = sig
  type value
  type t = Const of value | Var of string [@@deriving show]
  val zero: t
end

module type VALUE = sig
  type t 
  val from_int: int -> t
  val pp: Format.formatter -> t -> unit
end
                         
module Make (Value: VALUE) = struct
  type value = Value.t
  type t = Const of Value.t | Var of string [@@deriving show]
  let zero = Const (Value.from_int 0)
end
(* main.ml *)
module Int = struct
  type t = int [@@deriving show]
  let from_int x = x
end
           
module IntExpr = Expr.Make(Int)

let v = IntExpr.zero
let _ = Printf.printf "v=%s\n" (IntExpr.show v)

Compiling and execution gives:

> dune build ./main.bc
> dune exec ./main.bc

v=(Expr.Make.Const 0)

As a small refinement, you can add the deriving annotation to the type t in the module type VALUE as suggested by @CraigFe :

module type VALUE = sig
  type t [@@deriving show]
  val from_int: int -> t
end      

With this annotation, ppx_deriving add the corresponding generated function to the interface, which means one less possible mismatch between naming conventions.

Ok.
In this case, you can (and have to, to suppress warning) remove the line

  val pp: Format.formatter -> t -> unit

in the definition of VALUE signature.

Thanks again for your helpful suggestions