Skip to content

include functor #43

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 152 additions & 0 deletions rfcs/include_functor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# `include functor`

## Proposed change

The `include functor` extension eliminates a common source of boilerplate when
defining modules that include the results of functors. It adds the module item
form `include functor F`, where `F` must be a functor whose parameter can be
"filled in" with the previous contents of the module. For example, one can
write this:

```ocaml
module M = struct
type t = ...
[@@deriving compare, sexp]

include functor Comparable.Make
end
```

Traditionally, this would have required defining an inner structure `T` just to
have something the functor can be applied to:

```ocaml
module M = struct
module T = struct
type t = ...
[@@deriving compare, sexp]
end

include T
include Comparable.Make(T)
end
```

These two code fragments behave identically, except that in the first case the
module `M` won't have a submodule `T`.

### `include functor` in signatures

The feature can also be used in signatures:

```ocaml
module type F = functor (T : sig ... end) -> Comparable.S with type t = T.t

module type S = sig
type t
[@@deriving compare, sexp]

include functor F
end
```

This behaves as if we had written:

```ocaml
module type S = sig
type t
[@@deriving compare, sexp]

include Comparable.S with type t := t
end
```

Currently it's uncommon to define functor module types like `F` (there's no such
module type in
[`Base.Comparable`](https://ocaml.org/p/base/v0.16.3/doc/Base/Comparable/index.html),
for example). However, you can get the module type of a functor directly with
`module type of`, so the previous signature could equivalently be written:

```ocaml
module type S = sig
type t
[@@deriving compare, sexp]

include functor module type of Comparable.Make
end
```

The signature version of `include functor` is more surprising than its
counterpart in structures because the language does not otherwise have a
signature-level equivalent of functor application. That is, OCaml has functor
types like:
```ocaml
module type FT = functor (X : S) -> S'
```
But given an abstract `FT` the language has no way to ask for the signature that
would result from applying it to a module `M`. In other words, it lacks
a signature-level functor type application form like `FT(M)` that would yield
`S'[M/X]` (with appropriate stengthening). If such a form existed, then our
example above could be rewritten:
```ocaml
module type S = sig
module T : sig
type t
[@@deriving compare, sexp]
end

include F(T)
end
```

While this form is slightly novel for OCaml, it makes sense when `include
functor` is viewed as a lightweight "mixin" mechanism for the module system. In
settings with mixins/inheritence, it's standard to have some notion of mixin
type that is used both to classify the things you can inherit and also to create
signatures for modules/classes constructed using inheritance. In OCaml with
`include functor`, the types of the mixins are jsut functor types, and so the
thing used to create signatures for modules constructed using mixins should also
be functor types.

## Use cases

Occurrences of this pattern are frequent enough across the ecosystem that
`include functor` seems like a useful addition. Below are some examples where
`include functor` could be used (found by doing a quick
[Sherlocode](https://sherlocode.com/?q=include%20%5C%5BA-Z%5C%5D%5C%28%5C%5BA-Za-z0-9_.%5C%5D%5C%29%5C*%20%5C%3F%28%5C%5BA-Z%5C%5D)
search):

- [bap](https://github.com/BinaryAnalysisPlatform/bap/blob/95e81738c440fbc928a627e4b5ab3cccfded66e2/lib/regular/regular_bytes.ml#L62)
- [binsec](https://github.com/binsec/binsec/blob/395b8a8322c48b7d664af05b601261c28765fe0e/src/dba/dba_types.ml#L43)
- [boomerang](https://github.com/SolomonAduolMaina/boomerang/blob/895d5f18b76afbb8275c63ec5d9e71ae7c56e39f/lenssynth/naive_gen.ml#L77)
- [bonsai](https://github.com/janestreet/bonsai/blob/1a229341721c3be02c03ef046ccadabe6a434df7/web_ui/element_size_hooks/src/bulk_size_tracker.ml#L157)
- [dune](https://github.com/ocaml/dune/blob/425743ccaa353a65a36fa86f2551faf3afa6fa3b/src/dune_rules/lib.ml#L476)
- [herdtools7](https://github.com/herd/herdtools7/blob/79e2c831d68410b93bc48528b6195f857e2a1322/jingle/AArch64Arch_jingle.ml#L24)
- [sexp_diff](https://github.com/janestreet/sexp_diff_kernel/blob/7b98745bb6f969568fd5fa8283691fe11755bd5d/src/algo.ml#L59)
- [tezos](https://gitlab.com/tezos/tezos/-/blob/2d6821c6e99368eab442539c4ca6c2245794b0b8/src/proto_alpha/lib_protocol/nonce_hash.ml#L43)
- [tyxml](https://github.com/ocsigen/tyxml/blob/7429ec12b2145cc984d68286e8092a22b151fc38/implem/tyxml_xml.ml#L111)

This feature has already been implemented in Jane Street's
[branch](https://github.com/ocaml-flambda/flambda-backend/) of the OCaml
compiler, and is in active use here. It has proven popular: it is now used more
than 30k times in our internal codebase, and we believe our publicly released
libraries (like `Base` and `Core`) would benefit from it.

## Details and Limitations

To include a functor `F`, it must have a module type of the form:

```ocaml
F : S1 -> S2
```

or

```ocaml
F : S1 -> () -> S2
```

where `S1` and `S2` are signatures.

In the current implementation, `include functor` cannot be used in the
signatures of recursive modules.