package octez-proto-libs
Install
dune-project
Dependency
Authors
Maintainers
Sources
sha256=c6df840ebbf115e454db949028c595bec558a59a66cade73b52a6d099d6fa4d4
sha512=d8aee903b9fe130d73176bc8ec38b78c9ff65317da3cb4f3415f09af0c625b4384e7498201fdb61aa39086a7d5d409d0ab3423f9bc3ab989a680cf444a79bc13
doc/octez-proto-libs.protocol-environment/Tezos_protocol_environment/V5/Make/Lwt/index.html
Module Make.Lwt
Fundamentals
Promises
Promises for values of type 'a.
A promise is a memory cell that is always in one of three states:
- fulfilled, and containing one value of type
'a, - rejected, and containing one exception, or
- pending, in which case it may become fulfilled or rejected later.
A resolved promise is one that is either fulfilled or rejected, i.e. not pending. Once a promise is resolved, its content cannot change. So, promises are write-once references. The only possible state changes are (1) from pending to fulfilled and (2) from pending to rejected.
Promises are typically “read” by attaching callbacks to them. The most basic functions for that are Lwt.bind, which attaches a callback that is called when a promise becomes fulfilled, and Lwt.catch, for rejection.
Promise variables of this type, 'a Lwt.t, are actually read-only in Lwt. Separate resolvers of type 'a Lwt.u are used to write to them. Promises and their resolvers are created together by calling Lwt.wait. There is one exception to this: most promises can be canceled by calling Lwt.cancel, without going through a resolver.
val return : 'a -> 'a tLwt.return v creates a new promise that is already fulfilled with value v.
This is needed to satisfy the type system in some cases. For example, in a match expression where one case evaluates to a promise, the other cases have to evaluate to promises as well:
match need_input with
| true -> Lwt_io.(read_line stdin) (* Has type string Lwt.t... *)
| false -> Lwt.return "" (* ...so wrap empty string in a promise. *)Another typical usage is in let%lwt. The expression after the “in” has to evaluate to a promise. So, if you compute an ordinary value instead, you have to wrap it:
let%lwt line = Lwt_io.(read_line stdin) in
Lwt.return (line ^ ".")Callbacks
Lwt.bind p_1 f makes it so that f will run when p_1 is fulfilled.
When p_1 is fulfilled with value v_1, the callback f is called with that same value v_1. Eventually, after perhaps starting some I/O or other computation, f returns promise p_2.
Lwt.bind itself returns immediately. It only attaches the callback f to p_1 – it does not wait for p_2. What Lwt.bind returns is yet a third promise, p_3. Roughly speaking, fulfillment of p_3 represents both p_1 and p_2 becoming fulfilled, one after the other.
A minimal example of this is an echo program:
let () =
let p_3 =
Lwt.bind
Lwt_io.(read_line stdin)
(fun line -> Lwt_io.printl line)
in
Lwt_main.run p_3
(* ocamlfind opt -linkpkg -thread -package lwt.unix code.ml && ./a.out *)Rejection of p_1 and p_2, and raising an exception in f, are all forwarded to rejection of p_3.
Precise behavior
Lwt.bind returns a promise p_3 immediately. p_3 starts out pending, and is resolved as follows:
- The first condition to wait for is that
p_1becomes resolved. It does not matter whetherp_1is already resolved whenLwt.bindis called, or becomes resolved later – the rest of the behavior is the same. - If and when
p_1becomes resolved, it will, by definition, be either fulfilled or rejected. - If
p_1is rejected,p_3is rejected with the same exception. - If
p_1is fulfilled, with valuev,fis applied tov. fmay finish by returning the promisep_2, or raising an exception.- If
fraises an exception,p_3is rejected with that exception. - Finally, the remaining case is when
freturnsp_2. From that point on,p_3is effectively made into a reference top_2. This means they have the same state, undergo the same state changes, and performing any operation on one is equivalent to performing it on the other.
Syntactic sugar
Lwt.bind is almost never written directly, because sequences of Lwt.bind result in growing indentation and many parentheses:
let () =
Lwt_main.run begin
Lwt.bind Lwt_io.(read_line stdin) (fun line ->
Lwt.bind (Lwt_unix.sleep 1.) (fun () ->
Lwt_io.printf "One second ago, you entered %s\n" line))
end
(* ocamlfind opt -linkpkg -thread -package lwt.unix code.ml && ./a.out *)The recommended way to write Lwt.bind is using the let%lwt syntactic sugar:
let () =
Lwt_main.run begin
let%lwt line = Lwt_io.(read_line stdin) in
let%lwt () = Lwt_unix.sleep 1. in
Lwt_io.printf "One second ago, you entered %s\n" line
end
(* ocamlfind opt -linkpkg -thread -package lwt_ppx,lwt.unix code.ml && ./a.out *)This uses the Lwt PPX (preprocessor). Note that we had to add package lwt_ppx to the command line for building this program. We will do that throughout this manual.
Another way to write Lwt.bind, that you may encounter while reading code, is with the >>= operator:
open Lwt.Infix
let () =
Lwt_main.run begin
Lwt_io.(read_line stdin) >>= fun line ->
Lwt_unix.sleep 1. >>= fun () ->
Lwt_io.printf "One second ago, you entered %s\n" line
end
(* ocamlfind opt -linkpkg -thread -package lwt.unix code.ml && ./a.out *)The >>= operator comes from the module Lwt.Infix, which is why we opened it at the beginning of the program.
See also Lwt.map.
Convenience
Callback helpers
Lwt.map f p_1 is similar to Lwt.bind p_1 f, but f is not expected to return a promise.
This function is more convenient that Lwt.bind when f inherently does not return a promise. An example is Stdlib.int_of_string:
let read_int : unit -> int Lwt.t = fun () ->
Lwt.map
int_of_string
Lwt_io.(read_line stdin)
let () =
Lwt_main.run begin
let%lwt number = read_int () in
Lwt_io.printf "%i\n" number
end
(* ocamlfind opt -linkpkg -thread -package lwt_ppx,lwt.unix code.ml && ./a.out *)By comparison, the Lwt.bind version is more awkward:
let read_int : unit -> int Lwt.t = fun () ->
Lwt.bind
Lwt_io.(read_line stdin)
(fun line -> Lwt.return (int_of_string line))As with Lwt.bind, sequences of calls to Lwt.map result in excessive indentation and parentheses. The recommended syntactic sugar for avoiding this is the >|= operator, which comes from module Lwt.Infix:
open Lwt.Infix
let read_int : unit -> int Lwt.t = fun () ->
Lwt_io.(read_line stdin) >|= int_of_stringThe detailed operation follows. For consistency with the promises in Lwt.bind, the two promises involved are named p_1 and p_3:
p_1is the promise passed toLwt.map.p_3is the promise returned byLwt.map.
Lwt.map returns a promise p_3. p_3 starts out pending. It is resolved as follows:
p_1may be, or become, resolved. In that case, by definition, it will become fulfilled or rejected. Fulfillment is the interesting case, but the behavior on rejection is simpler, so we focus on rejection first.- When
p_1becomes rejected,p_3is rejected with the same exception. - When
p_1instead becomes fulfilled, call the value it is fulfilled withv. f vis applied. If this finishes, it may either return another value, or raise an exception.- If
f vreturns another valuev',p_3is fulfilled withv'. - If
f vraises exceptionexn,p_3is rejected withexn.
Pre-allocated promises
val return_unit : unit tLwt.return_unit is defined as Lwt.return (), but this definition is evaluated only once, during initialization of module Lwt, at the beginning of your program.
This means the promise is allocated only once. By contrast, each time Lwt.return () is evaluated, it allocates a new promise.
It is recommended to use Lwt.return_unit only where you know the allocations caused by an instance of Lwt.return () are a performance bottleneck. Generally, the cost of I/O tends to dominate the cost of Lwt.return () anyway.
In future Lwt, we hope to perform this optimization, of using a single, pre-allocated promise, automatically, wherever Lwt.return () is written.
val return_none : _ option tLwt.return_none is like Lwt.return_unit, but for Lwt.return None.
val return_nil : _ list tLwt.return_nil is like Lwt.return_unit, but for Lwt.return [].
val return_true : bool tLwt.return_true is like Lwt.return_unit, but for Lwt.return true.
val return_false : bool tLwt.return_false is like Lwt.return_unit, but for Lwt.return false.