Industrial strength, full-featured build system
Library jenga.tenacious
include Tenacious_intf.S
val version : string
val init : concurrency:int -> unit

To be called to set the amount of concurrency allowed before any call to exec.

include Core.Monad.S
type 'a t
val (>>=) : 'a t -> ( 'a -> 'b t ) -> 'b t

t >>= f returns a computation that sequences the computations represented by two monad elements. The resulting computation first does t to yield a value v, and then runs the computation returned by f v.

val (>>|) : 'a t -> ( 'a -> 'b ) -> 'b t

t >>| f is t >>= (fun a -> return (f a)).

module Monad_infix : sig ... end
val bind : 'a t -> f:( 'a -> 'b t ) -> 'b t

bind t ~f = t >>= f

val return : 'a -> 'a t

return v returns the (trivial) computation that returns v.

val map : 'a t -> f:( 'a -> 'b ) -> 'b t

map t ~f is t >>| f.

val join : 'a t t -> 'a t

join t is t >>= (fun t' -> t').

val ignore_m : 'a t -> unit t

ignore_m t is map t ~f:(fun _ -> ()). ignore_m used to be called ignore, but we decided that was a bad name, because it shadowed the widely used Pervasives.ignore. Some monads still do let ignore = ignore_m for historical reasons.

val all : 'a t list -> 'a list t
val all_ignore : unit t list -> unit t
module Let_syntax : sig ... end

These are convenient to have in scope when programming with a monad:

type 'a tenacious = 'a t
val all_unit : unit t list -> unit t
val map2 : 'a t -> 'b t -> f:( 'a -> 'b -> 'c ) -> 'c t
val both : 'a t -> 'b t -> ('a * 'b) t
val exec : 'a t -> name:Core.String.t Core.Lazy.t -> ('a * Heart.t) Async.Deferred.t

note that exec adds a new root in the observable tenacious graph, even if you call it from a function given to embed or lift.

val embed : ( cancel:Heart.t -> ('a * Heart.t) option Async.Deferred.t ) -> 'a t
val memoize : name:Core.String.t Core.Lazy.t -> 'a t -> 'a t
val bracket : 'a t -> running:( int -> unit ) -> finished:( 'a -> unit ) -> cancelled:( unit -> unit ) -> 'a t

This is most useful in combination with memoize: memoize (bracket ~running ~finished ~cancelled x). Without memoize, you can get multiple concurrent running..canceled and running..finished blocks even when your tenacious doesn't breaks its heart.

val uncancellable : 'a t -> 'a t
val desensitize : 'a t -> ('a * Heart.t) t
val lift : ( unit -> ('a * Heart.t) Async.Deferred.t ) -> 'a t

lift is specialization/simplification of embed

val cutoff : equal:( 'a -> 'a -> bool ) -> 'a t -> 'a t

cutoff is dangerous: it will delay heart breakage for the time it takes to re-compute the value so evaluating a memoize (cutoff x) might give you stale values even when memoize x wouldn't.

We have had a solution in the form of val protecting_cutoffs : 'a t -> 'a t that would only return a value after waiting for all cutoffs to finish. We removed the function right after f322aafe57c1 because it was unused and was preventing an optimization for Heart.or_broken.

val race : 'a t -> 'a t -> 'a t

race non-deterministically chooses the first computation to succeed, cancels the other.

Note that as it's non-deterministic it should be used with care. Ideally the final result should not depend on which result gets produced first.

module Stream : sig ... end

Stream provides two things:

module Result : sig ... end
val race_error : ( 'a, 'e ) Result.t -> ( 'b, 'e ) Result.t -> f:( 'a -> 'b -> 'c ) -> ( 'c, 'e ) Result.t

non-deterministically choose the faster one to fail. if neither fails, returns both. The race caveats apply.

val race_errors : ( 'a, 'e ) Result.t list -> ( 'a list, 'e ) Result.t
module Var : sig ... end

A mutable variable whose state can be watched as a Tenacious computation. 'a Var.t is conceptually a 'a Ref.t with Var.{create,get,set,replace} corresponding to Ref.{create,(!),(:=),replace}. The important difference is the watch function, that lets you construct tenacious computations that depend on the value of the variable.

module For_tests : Tenacious_intf.For_tests with type 'a t := 'a t