ppx
A preprocessor that rewrites the OCaml syntax tree between parsing and type checking. Derivers generate code from type declarations tagged [@@deriving name]; extension points expand nodes written [%name payload]. Neither sees any types.
OCaml's syntax has two hooks reserved for preprocessors. Attributes, [@...] and [@@...], annotate existing syntax and are ignored by the compiler if nothing handles them. Extension nodes, [%...], stand for syntax that must be expanded, and the compiler rejects any that are left.
Using a deriver from dune: preprocess names the rewriters, and the attribute says which types to derive for.
(library(name shapes)(preprocess (pps ppx_deriving.show)))type shape = Circle of float | Rect of float * float[@@deriving show](* generates pp_shape and show_shape *)
ppxlib is the library rewriters are written against. It gives a stable view of the AST across compiler versions, builds AST fragments from quotations like [%expr ...], and runs every registered rewriter in one pass over each file.
A rewriter runs before type checking, so it knows the shape of a type declaration but not what any expression's type is. Code that needs types either takes them from the declaration it was attached to, or is written so that the type checker resolves what the ppx could not.
see also
- Type-directed disambiguationType-directed disambiguation is OCaml's rule, since version 4.01, for deciding which record type a field label, or which variant type a constructor, belongs to when several in scope share the name: if the expected type is already known at that point of type inference, it decides; otherwise the most recently defined type with that name wins. It also lets labels and constructors be used without being in scope when the type is known. Because the outcome depends on what inference knows at each point, the -principal flag warns when a choice relied on information that a different inference order might not have had.
- Deriving strategyA deriving strategy is an explicit choice of how GHC produces a derived type class instance: stock, generated by the compiler from the constructors; newtype, reusing the wrapped type's instance by coercion; anyclass, an empty instance that takes the class's default methods; or via, reusing the instance of another type with the same representation. Several strategies can apply to the same declaration, with different results, so naming the strategy removes the ambiguity. The two coercing strategies are sound only when the class's methods can be coerced, which GHC checks with type roles.
- LR parsingBottom-up parsing driven by a table of states: the parser shifts tokens onto a stack, and reduces the top of the stack by a grammar rule when the next token says to. A grammar for which the table cannot be built without a choice has conflicts, reported as shift/reduce or reduce/reduce.
referenced by
- Lenses and Prisms in OCamlAn optics library whose kinds are a phantom polymorphic variant, and a ppx that derives lenses from records, prisms from variants, and both from field paths and patterns.
- ppx in PracticeFrom ppxlib boilerplate to a working [%where] extension: AST traversal, deriver registration, error nodes, and reading generated code.
further reading
- [1]The OCaml manual, “Attributes” and “Extension nodes”.
- [2]The ppxlib manual.
last updated