Up

module Type_generic

: sig

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

The term 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 sexp_of_t 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 'a.

#
module Variant_and_record_intf : module type of Variant_and_record_intf
#
module Helper : functor (A : Variant_and_record_intf.S) -> functor (B : Variant_and_record_intf.S) -> sig
#
type map = {
# map
: 'a . 'a A.t -> 'a B.t;
}
#
val map_variant : map -> 'a A.Variant.t -> 'a B.Variant.t
#
val map_record : map -> 'a A.Record.t -> 'a B.Record.t
end
#
module type Named = sig
#
type 'a computation
#
module Context : sig

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).

After a 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.

#
type t

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 'a Named.t

#
val create : unit -> t
end
#
type 'a t
#
val init : Context.t -> 'a Typename.t -> 'a t
#
val get_wip_computation : 'a t -> 'a computation
#
val set_final_computation : 'a t -> 'a computation -> 'a computation
#
val share : _ Std_internal.Typerep.t -> bool

It might be interesting to inline some computation for a few typerep if they appear several times within a typerep. This parameters will allow one to tweak the sharing between multiple occurences of the same typename. share = true means no inlining.

Note that not sharing recursive types will lead the of_typerep function to loop forever. Be careful when setting this.

An example where it is not suitable to share everything for example is typestruct. The typestruct of an int is a simple constructor called Int, naming it once and using the name to refere to it later within the typestruct does not lead to a shorter typestruct, and is in fact less readable. The benefit of the sharing depends on the computation, its memory and building costs.

end
#
module type Computation = sig
#
type 'a t
include Variant_and_record_intf.S with type 'a t := 'a t
#
val int : int t
#
val int32 : int32 t
#
val int64 : int64 t
#
val nativeint : nativeint t
#
val char : char t
#
val float : float t
#
val string : string t
#
val bool : bool t
#
val unit : unit t
#
val option : 'a t -> 'a option t
#
val list : 'a t -> 'a list t
#
val array : 'a t -> 'a array t
#
val lazy_t : 'a t -> 'a lazy_t t
#
val ref_ : 'a t -> 'a Pervasives.ref t
#
val function_ : 'a t -> 'b t -> ('a -> 'b) t
#
val tuple2 : 'a t -> 'b t -> ('a * 'b) t
#
val tuple3 : 'a t -> 'b t -> 'c t -> ('a * 'b * 'c) t
#
val tuple4 : 'a t -> 'b t -> 'c t -> 'd t -> ('a * 'b * 'c * 'd) t
#
val tuple5 : 'a t -> 'b t -> 'c t -> 'd t -> 'e t -> ('a * 'b * 'c * 'd * 'e) t
#
val record : 'a Record.t -> 'a t
#
val variant : 'a Variant.t -> 'a t
#
module Named : Named with type 'a computation := 'a t
end
#
module Make_named_for_closure : functor (X : sig
#
type 'a input
#
type 'a output
#
type 'a t = 'a input -> 'a output
end
) -> Named with type 'a computation := 'a X.t

Not all computations are arrow types. For example:

'a computation = Type_struct.t 'a computation = Type_hash.t

However, arrow types computation such as of_sexp, sexp_of, json_of, etc. are such a standard case that is seems reasonable to share this extra layer of functor for it to build the Named module.

#
module Ident : sig
#
type t

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.

end
#
module type S = sig
#
type 'a t
#
type 'a computation = 'a t
#
val ident : Ident.t
#
exception Not_implemented of string * string

generic_ident * typename or info

include Type_generic_intf.S with type 'a t := 'a t
#
val register0 : (module S0) -> unit

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 ...

#
val register1 : (module S1) -> unit
#
val register2 : (module S2) -> unit
#
val register3 : (module S3) -> unit
#
val register4 : (module S4) -> unit
#
val register5 : (module S5) -> unit
#
val register : 'a Std_internal.Typerep.t -> 'a computation -> unit

special less scary type when the type has no parameters. this is equivalent as using register0

#
val of_typerep : 'a Std_internal.Typerep.t -> [
| `generic of 'a computation
]

main function : compute the generic computation from the typerep

#
module Computation : Computation with type 'a t = 'a t

exported to build a computation on top of a previous one

end
#
module Make : functor (X : sig
#
type 'a t
#
val name : string
#
val required : Ident.t list
include Computation with type 'a t := 'a t
end
) -> S with type 'a t = 'a X.t

The name is used for debug information only in case of Broken_dependency. The required is to handle dependencies between generics at runtime. Example: if X is the module given to build a generic computation G that depends on three other computation A,B,C then X.required shall be A.ident ; B.ident ; C.ident

end