The library is divided into 3 parts (in the user area) which serve complementary purposes.
Preface.Specs
|
Contains all the interfaces of the available abstractions.
The specifications resemble the _intf suffixed
signatures found in other libraries in the OCaml ecosystem.
|
Preface.Make
|
Contains the set of functors (in the ML sense of the term)
for concretising abstractions. Schematically, a module in
Preface.Make takes a module (or modules) respecting a
signature described in Preface.Specs to produce a
complete signature (also described in Preface.Specs ).
|
Preface
|
Contains concrete implementations, constructs that implement
abstractions described in Preface.Specs by means of
the functors present in Preface.Make .
This library is, at least, an example of the use of
Specs and Make .
|
Abstraction implementations
Functor (in Haskell sense), Applicatives and monads are some of the best known abstractions in functional programming. Indeed, they allow recurrent problems to be solved in an elegant way. Generally, thanks to certain mechanisms linked to the languages that implement them (in Haskell, for example, using typeclasses), it is possible, by defining only a small subset of their combinators, to derive many others. So the purpose of "this part of the library" is to provide mechanisms for deriving combinators for a given type and a chosen abstraction, respecting OCaml programming idioms as much as possible.
Specifications
This module describes the specifications of the abstractions provided by Preface
. These specifications, which correspond to interfaces (module types
in OCaml terminology) serve as constraints for the functors
described in Preface.Make
and centralise the documentation. Using a separate module allows cyclic dependencies to be resolved if one module can be described by another module and vice versa.
Concretisations
In order to produce embodiments for the abstractions described in Preface.Specs
, Preface.Make
offers a collection of functors that take modules constrained by the interfaces described in Preface.Specs
to produce modules that respect the more complete interfaces also described in Preface.Specs
.
Concepts, Naming and Terminology
The modular design of Preface may seem a little intimidating at first glance. Let's look at the logic of the cut to understand how best to use it to describe new concretizations of abstractions.
Abstractions must respect a minimum interface, however, sometimes there are several paths to describe the abstraction. For example, building a Monad
on a type requires a return
(or pure
depending on the convention in practice) and:
bind
/>>=
- or
map
and join
- or sometimes
>=>
In addition, on the basis of these minimum combinators, it is possible to derive other combinators. However, it happens that these combinators are not implemented in an optimal way (this is the cost of abstraction). In the OCaml ecosystem, the use of polymorphic variants is sometimes used to give the user the freedom to implement, or not, a function by wrapping the function definition in a value of this type:
val f : [< `Derived | `Custom of 'a -> 'b ]
Instead of relying on this kind of (rather clever!) trick, we decided to rely mainly on the module language.
To make it easy to describe the embodiment of an abstraction, but still allow for the possibility of providing more efficient implementations (that propagate new implementations on aliases, such as infix operators, or functions that use these functions), Preface proposes a rather particular cut.
Each abstraction is broken down into several sub-modules:
Core
|
This module describes all the fundamental operations. For example,
for a monad, we would find return, map ,
bind , join and
compose_left_to_right
|
Operation
|
The module contains the set of operations that can be described
using the Core functions.
|
Infix
|
The module contains infix operators built on top of the
Core and Operation .
|
Syntax
|
The module contains the let operators (such as
let* and let+ for example), built with
the Core and Operation functions.
|
Sometimes it happens that some modules are not present (e.g. when there are no infix operators) or sometimes some additional modules are added, but in general the documentation is clear enough.
The functors exposed in Preface.Make
allow you to build each component one by one (Core
, Operation
, using Core
, and Infix
and Syntax
using Core
and Operation
) and then group all these modules together to form the abstraction. Or use the Happy Path, which generally offers a similar approach to functors which builds Core
but builds the whole abstraction.
Although it is likely that the use of the Happy Path covers a very large part of the use cases and that it is not necessary to concretise every abstraction by hand, it is still possible to do so.
In addition, it is sometimes possible to describe one abstraction by specialising another. In general, these specialisations follow this naming convention: From_name (More_general_module)
or To_name (Less_general_module)
and sometimes you can build a module on top of another, for example Selective on top of Applicative and the naming follows this convention: Over_name (Req)
, ie: Selective.Over_applicative
.
Standard library
Whereas the previous section dealt mainly with the concretisation of abstractions (using functor machinery). This section documents the standard Preface library. A collection of already implemented abstractions for relatively common data structures.
Common datatypes
Collection
Error handling
Functions
There are some (monad or comonad) transformers defined in Spec/Make
. In Stdlib
these are some concretised version using Identity
as inner monad or comonad.
Static Analysis
Applicatives
, Selectives
, Profunctors
and Arrows
allow, contrary to monads, to perform static analyses on calculation workflows. Over
and Under
allow optimistic or pessimistic approximations.