-
Notifications
You must be signed in to change notification settings - Fork 1
Code conventions
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.
act
tries to follow the Jane Street style guide.
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 usesIntf
instead, but this is being phased out. -
Basic
: something that needs to be passed into aMake
functor to produce aS
.Basic_foo
lines up withS_foo
andMake_foo
. Some older code confusingly usesS
here, but this is being phased out. -
Make
: a functor that producesS
. -
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 likeS
, but instead adds extra functionality to an existing module signature. -
Extend
: a functor that producesExtensions
. -
My
: modules prefixed by this are usually extensions to existingCore
orBase
modules; they're differently named to signal to readers that their functionality isn't standard.
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).
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.
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).
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), useC.With_errors
; each function is namedxyz_m
wherexyz
is its non-monadic equivalent. - ...inside another monad with module
M
, useOn_monad (M)
; this builds a module with the same signature asWith_errors
, but over the other monad.
To use this interface on lists and options, use Travesty.T_list
and Travesty.T_option
.