package primavera
Install
dune-project
Dependency
Authors
Maintainers
Sources
sha256=37c94e820b973564844d96a91717684ca909777104cc8cf28ce32ca4fd005225
sha512=524aa638b859ba236793fbb6eb24c5e6315b3a6eede47661e84873c01872579fc4c9fc26e4f380785b79784ab1580c3a9e588f9d6e21419979c3d38d7e5990a8
doc/primavera/Primavera/index.html
Module PrimaveraSource
As mentioned in the following article, sometimes the trivial ways of abstracting effects or dependencies in OCaml offer ‘too much power’ (type construction through modules or continuation control for effects).
The goal of Primavera is to provide an approach compatible with inference to control its dependencies by taking advantage of OCaml's type inference and using objects.
This approach, although more limited than effects and modules, allows operations to be captured as handlers using row polymorphism of objects.
Identity
Primavera is built on top of an identity monad to abstract dependencies whose normal form is a regular value.
include Sig.S1 with type 'a output = 'a
Types
The type of expression that dependency injection must produce.
The type that describes a calculation that uses dependency injection. 'a is the normal form of the programme and 'handler is the object that describes the set of dependencies.
Running and performing
Performs a computation with a given handler.
Performs a computation with a given handler.
Convert a regular function (which takes an handler) into an performable effect.
Convert a regular value (which takes an handler) into an performable effect.
local f comp is the contramap (on the environment). This allows it to be changed in a computation.
Regular functions
map f x mapping from f over x. Lift a function from 'a -> 'b to a function from 'a t -> 'b t.
replace x comp discard the result of comp by x.
ignore comp same as regular ignore function but for effectful computation.
zip a b is the monoidal product between a and b.
join comp flatten the result of comp.
compose f g x is the kleisli composition of f and g.
map2 lift a function 2-arity function.
val map3 :
('a -> 'b -> 'c -> 'd) ->
('a, 'handler) t ->
('b, 'handler) t ->
('c, 'handler) t ->
('d, 'handler) tmap3 lift a function 3-arity function.
val map4 :
('a -> 'b -> 'c -> 'd -> 'e) ->
('a, 'handler) t ->
('b, 'handler) t ->
('c, 'handler) t ->
('d, 'handler) t ->
('e, 'handler) tmap4 lift a function 4-arity function.
val map5 :
('a -> 'b -> 'c -> 'd -> 'e -> 'f) ->
('a, 'handler) t ->
('b, 'handler) t ->
('c, 'handler) t ->
('d, 'handler) t ->
('e, 'handler) t ->
('f, 'handler) tmap5 lift a function 5-arity function.
Infix Operators
Binding Operators
Included operators
f <$> x is map f x (infix version of map)
a <$ x is replace a x (infix version of replace)
x $> a is replace a x (infix (flipped) version of replace)
f <*> x is apply f x (infix version of apply)
a <&> b is zip a b (infix version of zip)
a <* b Perform a and b but discard b.
a *> b Perform a and b but discard a.
a << b Sequentially perform a following by b but discard b.
a >> b Sequentially perform a following by b but discard a.
m >>= f is bind m f (infix version of bind)
f =<< m is bind m f (infix (flipped) version of bind)
f <=< g is compose f g (infix version of compose)
f >=> g is compose g f (infix (flipped) version of compose)
let+ x = m in f x is map (fun x -> f x) m.
(binding version of map)
let+ x = m and+ y = n in f x y is map (fun (x, y) -> f x y) (zip m n).
(binding version of zip)
let* x = m in f x is bind m (fun x -> f x).
(binding version of bind)
let* x = m and* y = n in f x y is bind (zip m n) (fun (x, y) -> f x y).
(binding version of zip)
Traversable API
module Traversable
(T : Sig.Req.T1 with type 'a applicative = 'a output) :
Sig.T1
with type 'a foldable := 'a T.foldable
and type ('a, 'handler) applicative := ('a, 'handler) tTraversable interface on top of Req.S1 for S1
Prebuilt instances
Prebuilt
Common monads that are already preconfigured as standard forms of effect abstraction.
Complete interface built on top of Req.S1.
Complete interface built on top of Req.S2.
Requirements
module Req = Sig.ReqBuilding a Reader
Example
As is often the case, to demonstrate abstraction of effects or dependency injection, we use the usual teletype example. A programme capable of interacting with the console to print and read.
We begin by describing all possible interactions in our programme, the primitives read_line and print in an Fx module using the function Primavera.perform:
module Fx = struct
open Primavera
let print message = perform (fun handler -> handler#print message)
let print_line message = print (message ^ "\n")
let read_line () = perform (fun handler -> handler#read_line)
endThe handler passed via the Primavera.perform function is used to call one of its methods (and allow inference to track the operations that can be executed).
We can observe the signature of our module to understand how operations propagate through the type system.
# #show_module Fx;;
module Fx :
sig
val print : 'a -> ('b, < print : 'a -> 'b; .. >) Primavera.t
val print_line : string -> ('a, < print : string -> 'a; .. >) Primavera.t
val read_line : unit -> ('a, < read_line : 'a; .. >) Primavera.t
endNow that we have our operations, we can use them to describe the ancient teletype programme! To do this, we use the let* (Primavera.bind) operator to sequence operations with effects.
let teletype () =
let open Primavera in
let* () = Fx.print_line "What is your name?" in
let* name = Fx.read_line () in
Fx.print_line ("Hello " ^ name)
;;If we inspect the type of our teletype programme, we can observe several things:
# let t = teletype ;;
val t :
unit ->
(unit, < print : string -> unit; read_line : string; .. >) Primavera.t =
<fun>A primavera programme generally takes the following form: 'input -> ('outpout, <set_of_operation; ..> t).
Here, we can see that our programme takes unit and returns unit, and that it propagates the following operations:
printwhich takes astringand returnsunitread_linewhich returns astring.
Now that we have described a programme, we need to execute it, providing concrete implementations for the operations that can be propagated, using Primavera.run and giving an object to the flag handler:
# Primavera.run
~handler:object
method print message =
print_string message
(* Here we hook the call of read_line to
works in test-context *)
method read_line =
"Xavier"
end
teletype
()
What is your name?
Hello Xavier
- : unit = ()Internal Modules
module Sig : sig ... endSet of reusable interfaces.