Skip to content

Code conventions

Matt Windsor edited this page Dec 30, 2018 · 5 revisions

The act codebase mostly follows the following conventions.

NOTE: Being the first OCaml project of at least one of the authors, some of the code is markedly cleaner and more adherent to these conventions than the rest of it. Eventually (limit approaching heat death of the universe), the rest of the codebase'll get brought in line.

General style

act tries to follow the Jane Street style guide.

Module structure

Naming conventions

Currently, module and functor naming is all over the place, but we're slowly converging on the following conventions:

  • S: a main user-facing signature, representing a complete piece of functionality, that can be implemented in several different ways. If there are multiple different signatures, they're usually disambiguated with _extra_information on the end. Some older code uses Intf instead, but this is being phased out.
  • Basic: something that needs to be passed into a Make functor to produce a S. Basic_foo lines up with S_foo and Make_foo. Some older code confusingly uses S here, but this is being phased out.
  • Make: a functor that produces S.
  • M: usually used for the inner module whenever we're including the result of a functor inside a module.
  • Extensions: a signature that doesn't represent a complete unit like S, but instead adds extra functionality to an existing module signature.
  • Extend: a functor that produces Extensions.
  • My: modules prefixed by this are usually extensions to existing Core or Base modules; they're differently named to signal to readers that their functionality isn't standard.

Interfaces, implementations, and intf

Some modules in act---usually those that contain Basic/S/Make structures---have interfaces (.mli files) that immediately include implementation files with the suffix _intf. This is a convention inspired by Core/Base, and prevents duplication of module types across both the .mli file and .ml file (as both can include various bits of the _intf file).

Handling state

Generally, act prefers an immutable, monadic approach to handling state. This has the following advantages:

  • Making state an opt-in, syntactically heavyweight concept helps restrict state to areas where it genuinely makes things easier;
  • Making state something that explicitly needs to be sequenced makes ordering of stateful operations easier to understand when folding and iterating over bits of assembly;
  • It's easy to take snapshots of intermediate state and hold onto them, without accidentally receiving state updates from later computations. This, in turn, makes it easy to compare changes in state.

(To be fair, it also has disadvantages---lower performance, making stateful programming more tedious, etc.)

The main vehicle for passing state around in computations is the state monad, which in act lives in Utils.State. The sanitiser context in Lib.Sanitiser_ctx is an example of this pattern in action.

Traversing data structures

Most traversable data structures in act implement the traversable pattern from the Travesty library. This is, effectively, a variant of Haskell's Traversable typeclass.

This allows mapping, folding, and both-at-the-same-time in terms of a monad like Ctx (sanitiser context) or Or_error (error handling).

Usage

To use a container module C that implements Travesty.Traversable.SN_container...

  • ...outside a monad, just use the usual Container-style functions (C.map, C.fold, C.iter, C.mapi, etc).
  • ...inside the Or_error monad (eg, when doing a traversal that can fail), use C.With_errors; each function is named xyz_m where xyz is its non-monadic equivalent.
  • ...inside another monad with module M, use On_monad (M); this builds a module with the same signature as With_errors, but over the other monad.

To use this interface on lists and options, use Travesty.T_list and Travesty.T_option.

Clone this wiki locally