Library
Module
Module type
Parameter
Class
Class type
Provider - Dynamic Dispatch with Traits.
A provider is a construct that implements a set of functionality that a library typically needs in order to provide certain functionality to a end user.
The module is divided into several submodules:
Trait
: To identify functionality.Binding
: Associates a Trait with an implementation for it.Private
: Used for testing purposes.The main module provides the following functionalities:
This module is inspired by the Eio.Resource
module and provides a way to parametrize code when a library either doesn't want to or can't commit to a specific implementation.
module Trait : sig ... end
module Binding : sig ... end
implement trait ~impl:(module Impl)
says to implement trait
with Impl
. The module Impl
provided must have the right module type as specified by the type of trait
.
The tags associated with the trait
are ignored at this stage. The handling of the tags happens at the provider building stage, not at the granularity of each Trait. This means that the implement
function focuses solely on creating the implementation, without considering the tags that indicate which Traits are supported by the provider.
A provider is essentially a collection of bindings, associating each Trait it contains with an implementation for it. Each Trait provides a specific functionality (one Trait implementation = one first-class module with type t = 't).
't
is the internal state of the provider.'tags
indicate which functionality are supported by the provider. It is a phantom type using polymorphic variants. To give an example, in the tests for this library, we have two modules defining each their own tag:module Directory_reader = struct
type tag = [ `Directory_reader ]
end
module File_reader = struct
type tag = [ `File_reader ]
end
Then, the type of a provider whose internal type is state
, implementing both Traits would be:
(state, [ Directory_reader.tag | File_reader.tag ]) Provider.t
make bindings
create a new provider from a list of bindings. It only keeps the last implementation supplied for each Trait, from left to right. This means that the resulting provider will not contain any duplicate Traits, and the order of the bindings in the input list can affect its contents.
bindings t
returns a list of bindings with the Traits that the provider t
supports. See also extend
.
extend t ~with_
extends the provider t
and returns a new provider that includes both the original and additional bindings. The resulting provider only contains the last occurrence of each Trait, prioritizing the rightmost elements in the combined list bindings t @ with_
.
A lookup operation is used to retrieve the implementation of a specific Trait implementation from a provider.
val is_empty : ('t, _) t -> bool
is_empty t
checks if a provider t
implements any Traits. An empty provider may be created using make []
. It will cause any lookup operation to fail. It can be useful for initializing data structures or providing a base case for algorithms that process providers.
lookup t ~trait
retrieves the implementation for a given trait
from a provider.
If the provider has correctly exported their implementation using the appropriate tags, the compiler will ensure that this function does not fail in user code (a failure of this function would typically indicate a programming error in the provider's setup).
In the rare case where a provider has not correctly exported the tags of their implementation, this function will raise an internal exception. The exception is not exported, because it is not raised assuming a correct usage of the library.
You can find examples of incorrect usage in the tests of this library (e.g. "test__invalid_tags.ml").
lookup_opt t ~trait
returns the implementation of the trait
if present (Some implementation
) or indicates that the Trait is not implemented (None
).
This is particularly useful in scenarios where a part of a program needs to adapt behavior at runtime based on whether certain functionalities are available or not.
implements t ~trait
says wether a provider implements a Trait. This is true
iif lookup_opt t ~trait
returns Some _
.
A packed provider is a pair of a value and a set of Traits that a provider implements on that value. This is an OCaml value that behaves roughly like an object, but isn't one.
module Private : sig ... end
This module is exported for testing purposes only.