A computation is the type of an operation that can be applied to various different kind of types. It is expressed as a type with one parameter:
type 'a computation
Examples of computation:
type sexp_of_t = ('a -> Sexp.t) computation type type_struct = Type_struct.t computation
generic is used to refer to a specific implementation of a computation
whose concrete implementation is programmed using the type representation of values.
For example, when one uses
with sexp as a way to implement the
computation, the technique used is code generation at compile time. Another approach
is to define a generic function
sexp_of_t that inspects the representation of the
type at runtime.
This module offers an abstraction over type rep in order to implement generics in a efficient way.
Provided from a user enough pieces of implementation regarding a particular computation, this module returns essentially the following function:
(** main function : get the computation from the typerep *)
val of_typerep : 'a Typerep.t ->
`generic of 'a computation
that allows one to get the generic computation operating on a given type
Work in progress representation of a computation. This is mostly used to handle
recursive types. While building a computation on a recursive type, one needs to have
some computation available for the location where the type appears recursively.
init will be called once on each new type_name met during the traversal of a type.
Each time the same type is encountered again,
get_wip_computation will be called.
At the end of the traversal of that particular type,
set_final_computation will be
called, offering as a way to "close" the wip representation.
'a t can be mutable
(and is likely to be in practice).
set_final_computation is performed and return a final computation C for a
type_name, C will be memoized and returned for each further occurrences of the same
type_name inside the typerep, going further on.
Mutable context used to memorize some info during the traversal of a typerep.
A new context is created before starting to enter the toplevel of a typerep.
Then it is passed to all
init calls that happen during the traversal of it.
The user of the generic functor is free to stuff there whatever context needs to be
available while creating a new value of type
Not all computations are arrow types. For example:
'a computation = Type_struct.t
'a computation = Type_hash.t
However, arrow types computation such as
json_of, etc. are
such a standard case that is seems reasonable to share this extra layer of functor for
it to build the
Runtime identifier for a generic computation. This is essentially a string whose purpose is to give reasonable error messages in case the dependency requirements for a generic are not met at runtime.
The field called
required is needed in order to build a generic computation module.
It is used to establish a set up that would explicitly list all the computation that
are required by an other computation to work.
Generic computations are a way to build dynamically some operations on types. It is possible to build computation on top of each other. This ident type will be the key to talk about other computations at the point of setting up the dependencies.
generic_ident * typename or info
Extending an existing generic for a particular type name
The use of first class modules there is essentially because we cannot talk about a variable of kind * -> k val register1 : 'a 't Typerep.t -> ('a computation -> 'a 't computation) -> unit ...
name is used for debug information only in case of Broken_dependency.
required is to handle dependencies between generics at runtime.
X is the module given to build a generic computation
G that depends on three
A,B,C then X.required shall be
A.ident ; B.ident ; C.ident