M code/pds.conf +5 -0
@@ 48,6 48,11 @@ debug = { extra_makefile_lines = [ "OCAM
"\tjs_of_ocaml $(BYTE_TARGET)" ]}
+[src.byocm_demo]
+type = "exec"
+install = false
+deps = [ "byocm_fut", "byocm_sched" ]
+
[tests.byocm_fut]
deps = [ "byocm_fut" ]
A => code/src/byocm_demo/byocm_demo.ml +23 -0
@@ 0,0 1,23 @@
+let loop s =
+ let open Byocm_fut.Infix_monad in
+ let rec loop' = function
+ | 0 ->
+ Byocm_fut.return ()
+ | n ->
+ print_endline s;
+ Byocm_sched.sleep 1.0
+ >>= fun () ->
+ loop' (n - 1)
+ in
+ loop' 10
+
+let run () =
+ let open Byocm_fut.Infix_monad in
+ let fut1 = loop "Hello" in
+ let fut2 = loop "World" in
+ fut1
+ >>= fun () ->
+ fut2
+
+let () =
+ Byocm_sched.run run
M code/src/byocm_fut/byocm_fut.ml +65 -17
@@ 1,5 1,22 @@
+(* A future. This has no type defined for it because we are going to play
+ tricks on the type system. Specifically, we want be able to set the value of
+ a future at a later point, which means it's mutable. But we also want to
+ support sub-typing. Being mutable means the compiler gets upset about that.
+ So instead we'll not let the compile know that the underlying type is mutable
+ and we'll make sure our API is "set once". This type, [t], is what the
+ outside world will see as the type of a future, but internally we'll convert
+ it to and from a type called [u].*)
type +'a t
+(* The actual type of a future and a promise. A future and a promise are the
+ exact same thing underneath, they just have different APIs. A future has a
+ mutable state. When a promise is set, the state transitions from
+ undetermined to determined, and the watchers are executed with the value. A
+ future can also be an alias to another one (explained later).
+
+ When a future is undetermine, a [bind] call causes a watcher to be added to
+ the list of watchers. A watcher is a function that will be called with the
+ bound value. *)
type 'a u = { mutable state : 'a state }
and 'a state = [ `Det of 'a
| `Undet of 'a undet
@@ 7,9 24,14 @@ and 'a state = [ `Det of 'a
]
and 'a undet = { mutable watchers : ('a -> unit) list }
+(* Convert to/from t/u. This is just identity, and again this is done because
+ we need to trick the compiler for sub-typing reasons. *)
external t_of_u : 'a u -> 'a t = "%identity"
external u_of_t : 'a t -> 'a u = "%identity"
+(* If a future is an alias, get the actual value it belongs to. This should
+ probably be improved to set the future's alias value to the final future
+ found if the path of aliases is really long. *)
let rec collapse_alias = function
| { state = `Alias u } -> collapse_alias u
| u -> u
@@ 17,8 39,14 @@ let rec collapse_alias = function
module Promise = struct
type 'a fut = 'a t
type 'a t = 'a fut
+
let create () = t_of_u { state = `Undet { watchers = [] } }
+
external future : 'a t -> 'a fut = "%identity"
+
+ (* Set a promise to a value and execute all of its watchers in the process and
+ change the promise's state to determined. If the promise is anything OTHER
+ than undetermined, then fail. *)
let set t v =
let u = u_of_t t in
match u with
@@ 31,31 59,51 @@ module Promise = struct
end
let return v = t_of_u { state = `Det v }
+
+(* Bind is probably the most complicated function here. The goal of bind is to
+ take a future, and a function to execute with the value of the future once
+ it's determined and set things up to make that happen. A new future will be
+ made which will hold the result of applying the function so others can then
+ bind to that. This way you can, for example, request a value from a REST
+ API, transform it once it's received, and then do something with that value,
+ and so on.
+
+ If the future is already determined, the function can simply be called and
+ the future it returns be returned. Otherwise we need to make a watcher and
+ connect some futures. *)
let bind : 'a t -> ('a -> 'b t) -> 'b t = fun t f ->
let u = collapse_alias (u_of_t t) in
match u with
| { state = `Det v } ->
+ (* If determined, just call and return *)
f v
| { state = `Undet undet } ->
+ (* Undetermined, so we need a new promise and future pair. *)
let p = Promise.create () in
let fut = Promise.future p in
- let w =
- fun v ->
- let u = collapse_alias (u_of_t (f v)) in
- match u with
- | { state = `Det v' } ->
- Promise.set p v'
- | { state = `Undet undet' } ->
- begin match collapse_alias (u_of_t fut) with
- | { state = `Undet undet'' } as fut' ->
- undet'.watchers <- undet'.watchers @ undet''.watchers;
- fut'.state <- `Alias u
- | { state = `Det _ }
- | { state = `Alias _ } ->
- assert false
- end
- | _ ->
- assert false
+ (* Our watcher gets called with the value that [t] was bound to. *)
+ let w v =
+ (* Apply the value to [f], which returns a new future. *)
+ let u = collapse_alias (u_of_t (f v)) in
+ match u with
+ | { state = `Det v' } ->
+ (* If that future is already determined, we can set its value to
+ [p]. *)
+ Promise.set p v'
+ | { state = `Undet undet' } ->
+ (* Otherwise we are going to make [fut] an alias to the future that
+ was returned. This means combining its watchers and changing the
+ state of [fut]. *)
+ begin match collapse_alias (u_of_t fut) with
+ | { state = `Undet undet'' } as fut' ->
+ undet'.watchers <- undet'.watchers @ undet''.watchers;
+ fut'.state <- `Alias u
+ | { state = `Det _ }
+ | { state = `Alias _ } ->
+ assert false
+ end
+ | _ ->
+ assert false
in
undet.watchers <- w::undet.watchers;
fut
M code/src/byocm_fut/byocm_fut.mli +22 -4
@@ 8,24 8,42 @@
An API will generally be written like:
- [ let p = Promise.create () in
- let callback v = Promise.set p v in
- schedule_work callback;
- Promise.future p ] *)
+ {[let some_api () =
+ let p = Promise.create () in
+ let callback v = Promise.set p v in
+ schedule_work callback;
+ Promise.future p ]} *)
+
+(** A future, can be read but not set. *)
type +'a t
module Promise : sig
type 'a fut = 'a t
+
+ (** A promise, can be set but not read. *)
type 'a t
val create : unit -> 'a t
+
+ (** Turn a promise into a future. *)
val future : 'a t -> 'a fut
+
+ (** Set a promise, all listeners to the future will be called with the
+ value. *)
val set : 'a t -> 'a -> unit
end
+(** Turn a value into a future that has already been set to a value. *)
val return : 'a -> 'a t
+
+(** Execute the function when the input future is set to a value and return the
+ result of the function as a new future. The new future will be set to a
+ value once the function is run. *)
val bind : 'a t -> ('a -> 'b t) -> 'b t
+
+(** Query the state of a future. *)
val state : 'a t -> [ `Det of 'a | `Undet ]
module Infix_monad : sig
+ (** Infix notation for {!bind}. *)
val (>>=) : 'a t -> ('a -> 'b t) -> 'b t
end
M presentation/byocm.tex +4 -0
@@ 229,6 229,7 @@
\item Add some Unix APIs (send/recv/connect/accept).
\item Switch to something other than \texttt{Unix.select}.
\item Support canceling operations.
+ \item Applicatives.
\end{itemize}
\end{frame}
@@ 268,7 269,10 @@
\hlstd{}\hlstd{\ \ \ \ }\hlstd{}\hlopt{$>$$>$=\ }\hlstd{}\hlkwa{fun\ }\hlstd{}\hlopt{()\ {-}$>$}\hspace*{\fill}\\
\hlstd{}\hlstd{\ \ \ \ }\hlstd{}\hlkwd{run}\hlstd{'\ elm\ }\hlopt{(}\hlstd{n\ }\hlopt{{-}\ }\hlstd{}\hlnum{1}\hlstd{}\hlopt{)}\hlstd{}\hspace*{\fill}\\
\mbox{}
+\end{frame}
+\begin{frame}{Conclusion}
+
\end{frame}
\end{document}