package ppx_mixins
Install
dune-project
Dependency
Authors
Maintainers
Sources
sha256=ed411d59c016e8429dfd375eb80629412092f43cb05ed3469ff79a9a89001988
sha512=d5b853c7372c5979e029293549247ec441e1bd4f59b339f4bf173a24f673d2584f88ad63d69eca638fd4581183a0d8af58707b748ea54df9bcdc2270b20fae7a
doc/README.html
ppx_mixins
A ppxlib-based OCaml PPX that lets you compose module-type signatures inline using a [@@mixins ...] attribute on a type declaration.
Motivation
OCaml's include M with type t := foo idiom is the standard way to "mix in" the interface of a module type while substituting the primary type. Writing it by hand is repetitive, especially when several mixins share the same t := substitution. ppx_mixins lets you express this intent concisely:
module type S = sig
type my_type [@@mixins Printable + Comparable]
endexpands to:
module type S = sig
type my_type
include Printable with type t := my_type
include Comparable with type t := my_type
endInstallation
Add ppx_mixins to your project's dependencies and preprocessors in dune:
(library
(name my_lib)
(preprocess (pps ppx_mixins)))Syntax
type <name> [@@mixins <mixin> [+ <mixin> ...]]where each <mixin> is:
ModuleTypeName
ModuleTypeName (param [; param ...])and each param is one of:
Syntax | Meaning |
|---|---|
| equality constraint ( |
| destructive substitution ( |
Type can be any of:
Form | Example |
|---|---|
plain constructor |
|
|
|
single-param applied |
|
multi-param applied |
|
Multiple mixins are separated by +. Multiple params within a mixin's parentheses are separated by ;.
Automatic t substitution
Mixin module types conventionally name their primary type t. If you do not provide any constraint that binds t (either t = ... or t := ...), ppx_mixins automatically prepends type t := <name> for you. To suppress this, supply an explicit t constraint:
type my_type [@@mixins Weird (t := my_type; u := my_type)]
(* no extra t := is injected *)Examples
Single bare mixin
module type S = sig
type my_type [@@mixins Printable]
end
(* expands to:
type my_type
include Printable with type t := my_type *)Multiple bare mixins
module type S = sig
type my_type [@@mixins Printable + Comparable]
end
(* expands to:
type my_type
include Printable with type t := my_type
include Comparable with type t := my_type *)Equality constraints
module type S = sig
type my_type [@@mixins Mappable (key = string; value = int)]
end
(* expands to:
type my_type
include Mappable with type key = string
and type value = int
and type t := my_type *)Mixed equality and substitution with explicit t
module type S = sig
type my_type [@@mixins Mappable (key = string; value = int; t := my_type)]
end
(* expands to:
type my_type
include Mappable with type key = string
and type value = int
and type t := my_type *)Parameterised constraint RHS
module type S = sig
type my_type [@@mixins Container (elt = int list)]
end
(* expands to:
type my_type
include Container with type elt = int list and type t := my_type *)Multi-parameter constructors use the same parenthesised tuple syntax as in type expressions:
type my_type [@@mixins Mapper (result_ = (int, string) result)]Shorthand module type syntax
When the primary type in the expanded signature should simply be named t, you can skip the sig ... end wrapper entirely and write:
module type S = [%mixins Printable + Comparable]which expands to:
module type S = sig
type t
include Printable with type t := t
include Comparable with type t := t
endAll the same mixin syntax (params, constraints, +-separated lists) works identically to the [@@mixins] form. The only difference is that the implicit primary type is always t -- there is no way to choose a different name with the shorthand. If you need a different primary type name, use the full [@@mixins] attribute form.
Implementation overview
The rewriter is implemented as a global Ast_traverse.map registered via Driver.V2.register_transformation. It walks every sig block in both .ml and .mli files and runs a three-phase pipeline on each annotated type declaration:
- Parse — flatten the
Pexp_apply "+"expression tree into amixin list. - Inject — prepend
t := <type_name>to any mixin that has notbinding. - Desugar — emit
psig_type(without the attribute) followed by onepsig_includeper mixin.
See lib/ppx_mixins.ml for the full documented source.
Limitations
- Tuple types (
int * string) and function types (int -> string) on the RHS of a constraint are not supported. Wrap them in a type alias if needed. - Only
sigitems are rewritten;[@@mixins]on a type inside astructis not meaningful and will be left untouched (ppxlib will warn about an unused attribute).