Generating infix extension nodes with Ppxlib

Hi! I’m working on a PPX deriver for property-based testing that produces testing code utilizing Jane Street’s Core.Quickcheck library. Specifically, I would like my PPX to produce monadic bind expressions that use the library’s let%bind syntax (the infix form of the extension node [%bind ...]), such as the following:

(* sample an int from a monadic Quickcheck generator [gen_int], 
  using [ppx_jane] *)
let%bind x = gen_int in ...

Right now, my PPX deriver produces the expression above by calling the function pexp_extension (in Ppxlib.Ast_builder.Default) and using Metaquot quotations to produce a Pexp_extension AST node:

pexp_extension ({txt = "bind", ...},
      PStr [ pstr_eval ~loc [%expr let x = gen_int in ...] ...]) ...

This produces the following:

[%bind let x = gen_int in ...]

I understand from the OCaml language manual that the let%bind ... infix form desugars to [%bind ...].

I was wondering if it would be possible for my PPX deriver to directly produce the let%bind syntactic form via Ppxlib? I understand if this is infeasible since this desugaring is typically done by another PPX (ppx_jane).

Thanks!

let%bind p = e1 in e2 and [%bind let p = e1 in e2] are represented as the same AST, which you are generating correctly, so there’s nothing different you can do. It’s the same as how 1 + 2 and (+) 1 2 have the same AST.

Maybe your question is “when I pretty-print that AST, how I can get let%bind .. instead of [%bind let ...] ?”. For that, usually you can’t choose a specific concrete syntax, but it probably depends on the pretty printer. I don’t know what the compiler printer does. I think ocamlformat looks at AST positions to distinguish ASTs from let%bind ... vs ASTs from [%bind ...], and display the same syntax as the source code.

As a side remark, if you’re using metaquot, you should be able to use it some more to simplify your code further. All these should generate the same AST:

pexp_extension ~loc ({txt = "bind"; loc},
                     PStr [ pstr_eval ~loc [%expr let x = gen_int in ()] []])
pexp_extension ~loc ({txt = "bind"; loc},
                     PStr [ [%stri let x = gen_int in ()]])
pexp_extension ~loc ({txt = "bind"; loc},
                     PStr [%str let x = gen_int in ()])
[%expr let%bind x = gen_int in ()]

(and you could also use Ppx_let_expander.expand to directly expand the %bind code instead of requiring users of your ppx to also list ppx_let in their dune file, with the downside that the Ppx_let_expander API presumably gets breaking changes from times to times).

Thanks for the reply, especially the tips on using Metaquot to make the PPX code more succinct!
I’ll look into using the Ppx_let_expander API, and if that doesn’t work I’ll just make my PPX generate monadic code that uses the infix >>= operator (instead of let%bind ...).