override

PPX extension for overriding modules
README

This library extends OCaml syntax for overriding modules defined in
other compiled interface files. This library generalizes
ppx_import by allowing a whole module to be imported with all its
types, possibly with annotations. In particular, importing a whole
module can be convenient to apply ppx_deriving to a large family
of mutually inductive data types (see examples below).

Usage

Similarly to ppx_import, you may require the override package in
the staged_pps field of your dune file.

(library
  (name foo)
  (preprocess (staged_pps override ppx_deriving.show)))

You may use override in the toplevel with the help of
findlib.

#use "topfind";;
#require "override";;

Overriding submodules

In its simplest form, this syntax extension can be seen as a
mechanization of Gabriel Scherer's post about Overriding submodules
on Gagallium blog
(it is worth noticing that, thanks to GPR#1892, module overriding is
now free with OCaml 4.08, so readers interested about what this library can
still offer with new versions of OCaml may skip this section).

Let's begin with the same example as in the blog post, with the
following module defined in toto.ml.

let x = 1
module Tata = struct
  let y = 2
end
module Titi = struct
  let z = 3
end

In another file, say gagallium.ml, we can override the module
Toto to change the definition of y for a string, for example.

module%override Toto = struct
  module%override Tata = struct
    let y = "2"
  end
end

This works for interfaces too: we can write the following interface in
gagallium.mli, in the same lines as above.

module%override Toto : sig
  module%override Tata : sig
    val y : string
  end
end

This works with functors too: we can add for example a function diff
to modules generated by Map.Make (map_diff.ml).

module%override Map = struct
  module%override Make (X : OrderedType) = struct
    let diff m0 m1 =
      merge (fun key v0 v1 -> match v1 with None -> v0 | Some _ -> None) m0 m1
  end
end

This works with module types too: the following example changes the
definition of Hashtbl.HashedType to take a compare predicate instead
of equal (hashed_type_compare.ml).
Note that since there is no with module type constraint, the signature
of Hashtbl have to be expanded in the code generated by the notation
to remove the original HashedType definition.

module%override Hashtbl = struct
  module type HashedType = sig
    type t

    val compare : t -> t -> int

    val hash : t -> int
  end

  module Make (X : HashedType) = struct
    include Hashtbl.Make (struct
      type t = X.t

      let equal x y =
        X.compare x y = 0

      let hash = X.hash
    end)
  end
end

Importing types

If the imported module defines a type, we can override its definition
with the construction type t = _: this construction allows new attributes to
be added to the type.
For instance, the following example (adapted from ppx_import) derives
Longident.show from the definition of Longident.t
(longident_show.ml).

module%override Longident = struct
  type t = _  [@@deriving show]
end

let () =
  print_endline (Longident.show (Longident.parse "Foo.Bar.baz"))
(* Longident.Ldot (Longident.Ldot (Longident.Lident ("Foo"), "Bar"), "baz") *)

Several types may be imported at once with the construction
type t and ... and u = _.
All the types imported at once are redefined in a single mutually recursive
definition.

module%override Location = struct
  type t and 'a loc = _ [@@deriving show]
end

and co: importing mutually recursive types

Ending a type list with and co include the other types that are
defined in the same mutually recursive definitions as one of the types
explicitly listed.
Each added type carries the attributes given after and co.

The and co notation will be recognized if it ends the enumeration:
if you want to refer to an abstract type called co, you may refer to
it at another place in the enumeration.

module%overrive Example = struct
  type a and b = _ and co [@@deriving show]
end

[@@from: ... ]: renaming types

Names of the types can be changed: the type declaration introduces the
new type and the imported name is carried by the [@@from: ...]
notation.

module%override Location = struct
  type location = _ [@@from: t]
end

[%%types]: importing all the types and module types from a module

The notation [%%types] imports all the types and module types
defined in the currently overriden module, except types and moduel
types that have been overriden previously. Attributes given to
[%%types] are applied to each imported type. Types that were
mutually recursive in the imported module are defined as mutually
recursive in the overriden module (to gather independent types in a
single mutually recursive group, see the next section about the
[%%recursive] extension).

Attributes applied on [%%types] are applied once by declaration
group.

For example, we may try to derive show for all types of OCaml Parsetree,
using the package compiler-libs.common.

module%override Parsetree = struct
  [%%types] [@@deriving show]
end

We get the following error.

File "_none_", line 1:
Error: Unbound value Asttypes.pp_loc

We can fix the error by deriving show for Asttypes as well.

module%override Asttypes = struct
  [%%types] [@@deriving show]
end

module%override Parsetree = struct
  [%%types] [@@deriving show]
end

We will get errors for Location.pp and Longident.pp that we will
fix in the same way. More interestingly, with OCaml 4.07 and above, we
will get the following error.

File "_none_", line 1:
Error: Unbound value Stdlib.Lexing.pp_position

This error can be solved by overriding both the module Stdlib and
the submodule Lexing. All in all, show can be derived for Parsetree
and all its dependencies as below (parsetree_show.ml).
(This example supposes that OCaml 4.07 or above is used: for former versions
of OCaml, Lexing should just be imported as the other modules.)

module%override Stdlib = struct
  module%override Lexing = struct
    [%%types] [@@deriving show]
  end
end

module%override Longident = struct
  [%%types] [@@deriving show]
end

module%override Location = struct
  [%%types] [@@deriving show]
end
ov
module%override Asttypes = struct
  [%%types] [@@deriving show]
end

module%override Parsetree = struct
  [%%types] [@@deriving show]
end

In the case of Asttypes and Parsetree, the imported interfaces
only contain types, and all these types are redefined (with
[%%types]). When these two conditions are met, the modules
themselves are not included. Since Asttypes and Parsetree
interfaces have no implementations, there would have been a link-time
failure if they were included.

[%%symbols]: importing all the symbols from a module

The notation [%%symbols] can be used in signatures to import all
the symbols (types, module types, values and module declarations) that
are defined in the imported module. The following example defines
the signature of a module for ordered and hashed types by constructing
the union of Hashtbl.HashedType and Hashtbl.OrderedType
(ordered_hashed_type.ml).

module type S = sig
  module%import Hashtbl : sig
    module type%import HashedType = sig
      [%%symbols]
    end
  end

  module%import Map : sig
    module type%import OrderedType = sig
      type t [@@remove]
      [%%symbols]
    end
  end
end

[@@remove]: removing types

Types may be removed from signature with the annotation [@@remove].
Several types may be removed at once by listing them with and,
and the and co notation may be used as well.

module%override Asttypes = struct
  type constant [@@remove]

  [%%types] [@@deriving show]
end

Note that to exclude some types from the annotations carried by
[%%types] without removing them from the module, the types just
have to be imported before invoking [%%types].

module%override Asttypes = struct
  type constant = _

  [%%types] [@@deriving show]
end

If the excluded types have to be imported after [%%types] (for
instance, if the excluded types refer to the other types), it is
possible to remove them first and reimport them afterwards.

module%override Parsetree = struct
  type toplevel_phrase and co [@@remove]

  [%%types] [@@deriving show]

  type toplevel_phrase = _ and co
end

module%include, module%import, [%%recursive]: flattening structure

The notation module%include overrides a module and include it in the
current module.
In the following example, the types location and 'a loc are then
defined at top-level (and not in a module Location).

module%include Location = struct
  type location [@@from: t] and 'a loc = _ [@@deriving show]
end

The notation module%import overrides a module in the current module
without including the definitions that are not explicitely overriden.
In the following example, the type loc is defined at top-level as an
alias for Location.t, but the type 'a loc is not imported.

module%import Location = struct
  type loc = _ [@@from: t] [@@deriving show]
end

The notation recursive transforms a set of type definitions into a
single mutually recursive definition of types. The annotations put to
recursive are applied to one of the types (the first one). This can
be useful for some derivers. The recursive notation will reject
structures that contain other items than type definitions, but
module%import can be used inside recursive as long as the
structure contains only type definitions. In the following example,
location, 'a loc and longident are defined in a single mutually
recursive type definition.

[%%recursive
  module%import Location = struct
    type location = _ [@@from: t]

    type 'a loc = _
  end

  module%import Longident = struct
    type longident = _ [@@from: t]
  end]

Note that the notation becomes [%%recursive: ...] in a signature.

Self import of types declared in the interface file

The notation module%import is useful in particular to import the types
declared in the interface file. For example, the implementation file
self_import.ml can import the types declared in self_import.mli
with the following construction.

module%import Self_import = struct
  [%%types]
end

This construction can be used at the root of the implementation file
as well as in submodules, including functors.
For instance, the following interface file self_import.mli declares
a type, a module type and a functor, and some values.

type t = A | B

val x : t

module type S = sig
  type t

  val perform : t -> unit
end

module Make (X : S) : sig
  type t = X.t

  val perform_twice : t -> unit
end

The following implementation file self_import.ml imports these
definitions and provides the declared values.

module%import Self_import = struct
  [%%types]
end

let x = A

module Make (X : S) = struct
  module%import Self_import = struct
    module%import Make (X : S) = struct
      [%%types]
    end
  end

  let perform_twice t =
    X.perform t;
    X.perform t
end

[@@rewrite]: rewriting types

Type declarations can be annotated with [@@rewrite] attribute to use
them as rewriting rules for subsequent type importations. Let's consider
the following example.

module%override Location = struct
  type location = _ [@@from: t]

  type 'a loc = _
end

The type Location.t is renamed into Location.location but
the imported definition of loc still refers to Location.t, which can
be indesirable if that definition is used for deriving.
By putting the annotation [@@rewrite] to the definition of location,
the rewriting rule tlocation is applied to every type importation
that appears after the definition of location in the module.

module%override Location = struct
  type location = _ [@@from: t] [@@rewrite]
  (* Rewriting rules at this point: t -> location *)

  type 'a loc = _
  (* Will be defined as type 'a loc = { txt : 'a; loc : location },
     where the type of loc has been rewritten. *)
end
(* Rewriting rules at this point: Location.t -> Location.location *)

The scope of rewriting rules extends to the end of the outer-most
block that carries an override notation (module%override, recursive, ...).
The notation [%%rewrite ...] can be used to introduce a rewriting scope
explicitely (scopes can be nested, and rewriting rules in outer scopes apply
in inner scopes). Rewriting rules are promoted by qualifying the identifiers
accordingly when their scope extends after the module where they are defined.
For instance, the rule tlocation defined in the example above would
become Location.tLocation.location if applied outside the module
Location.

The notation [@@rewrite] can be applied to a type alias as well. In
the following example, Longident.t loc will be rewritten longident loc by the rewriting rule introduced by the first type declaration,
and then longident_loc by the second (rewriting rules are applied up
to reaching a fix point).

[%%types
  module%import Longident = struct
    type longident = _ [@@from: t] [@@rewrite]
    (* Rewriting rules at this point: t -> longident *)
  end
  (* Rewriting rules at this point: Longident.t -> longident *)

  type longident_loc = longident Location.loc [@@rewrite]
  (* Rewriting rules at this point:
     - Longident.t -> longident
     - longident Location.loc -> longident_loc

     The following rewriting rule is induced by composition:
     - Longident.t Location.loc -> longident_loc *)
]

When applied to a removed type, the rewriting rule is oriented in the
opposite direction. For instance, the following example transforms references
to 'a Asttypes.loc into 'a Location.loc.

module%override Asttypes = struct
  type 'a loc [@@rewrite] [@@remove]
  (* Rewriting rules at this point: 'a loc -> 'a Location.loc *)
end
(* Rewriting rules at this point: a Asttypes.loc -> 'a Location.loc *)

If the removed type is declared as a type alias, the right-hand side of the
declaration is used as the right-hand side of the rewriting rule.
This can be useful for instance to annotate the occurrences of some types .

For instance, the following example (adapted from ppx_import) derives
pp_package_type from the definition of Parsetree.package_type
(package_type.ml), relying on rewriting rules to add annotations
to the occurrences of Longident.t and Asttypes.loc.

[%%rewrite
  module%import Longident = struct
    type t = Longident.t [@printer Printtyp.longident]
          [@@rewrite] [@@remove]
    (* Rewriting rules at this point:
       - t -> Longident.t [@printer Printtyp.longident] *)
  end
  (* Rewriting rules at this point:
     - Longident.t -> Longident.t [@printer Printtyp.longident] *)

  module%import Asttypes = struct
    type 'a loc = 'a Asttypes.loc
          [@polyprinter fun pp fmt x -> pp fmt x.Asttypes.txt]
          [@@rewrite] [@@remove]
  end

  module%import Parsetree = struct
    type core_type = Parsetree.core_type [@printer Pprintast.core_type]
          [@@rewrite] [@@remove]

    (* Rewriting rules at this point:
       - Longident.t -> Longident.t [@printer Printtyp.longident]
       - 'a Asttypes.loc -> 'a Asttypes.loc
          [@polyprinter fun pp fmt x -> pp fmt x.Asttypes.txt]
       - core_type -> Parsetree.core_type [@printer Pprintast.core_type] *)

    type package_type = _ [@@deriving show]
  end]

The following example implements mechanically the transformation rules
described in the comments of ast/ast.ml from ppxlib.

(* "- replacing app [type ...] by [and ...] to make everything one
recursive block" *)
[%%recursive

  (* "- adding the type definitions for position, location, loc and longident"
     They are imported from Stdlib.Lexing.position, Location.t, 'a Location.loc,
     and Longident.t respectively. *)

  (* "- flattening all the modules"
     All the modules are imported with module%import. *)
  module%import Stdlib = struct
    module%import Lexing = struct
      type position = _ [@@rewrite]
    end
  end

  module%import Location = struct
    (* "- renaming a few types:
        - - Location.t -> location" *)
    type location = _ [@@from: t] [@@rewrite]

    type 'a loc = _ [@@rewrite]
  end

  module%import Longident = struc
    (* "- renaming a few types:
        - - Longident.t -> longident" *)t
    type longident = _ [@@from: t] [@@rewrite]
  end

  (* "- adding a type longident_loc = longident loc and replacing all
   the occurences of the latter by the former. This is so that we can
   override iteration an the level of a longident loc." *)
  type longident_loc = longident loc [@@rewrite]

  module%import Asttypes = struct
    (* "- removing Asttypes.constant (unused and conflicts with
       Parsetree.constant)" *)
    type constant [@@remove]

    type 'a loc [@@rewrite] [@@remove]

    [%%types] [@@rewrite]
  end

  module%import Parsetree = struct
    [%%types]
  end]
Install
Sources
v0.3.0.tar.gz
sha512=ab70f9d58edb5cd3ba78ff25b37a8426d8cfa170a14220be9c0c2c8df071d7b9804e8e40acbc01ead66b5e094020b8adf0b9aeb99830bd285c5c8ddf3aa846b5
Dependencies
refl
>= "0.2.0" & < "0.3.0"
metaquot
>= "0.2.0" & < "0.3.0"
metapp
>= "0.2.0" & < "0.3.0"
dune
>= "1.11.0"
ocaml
>= "4.03.0" & < "4.12.0"
Reverse Dependencies
clangml
= "4.0.0" | = "4.0.1"