# HG changeset patch # User Malcolm Matalka # Date 1566797435 -7200 # Mon Aug 26 07:30:35 2019 +0200 # Node ID 7f7f49199ca03c00afd830f24d916c8466a15142 # Parent b19dcaef25ca9341eb6e1f1bc0b944a385d4cdf8 ADD Concurrency demo diff --git a/code/pds.conf b/code/pds.conf --- a/code/pds.conf +++ b/code/pds.conf @@ -48,6 +48,11 @@ "\tjs_of_ocaml $(BYTE_TARGET)" ]} +[src.byocm_demo] +type = "exec" +install = false +deps = [ "byocm_fut", "byocm_sched" ] + [tests.byocm_fut] deps = [ "byocm_fut" ] diff --git a/code/src/byocm_demo/byocm_demo.ml b/code/src/byocm_demo/byocm_demo.ml new file mode 100644 --- /dev/null +++ b/code/src/byocm_demo/byocm_demo.ml @@ -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 diff --git a/code/src/byocm_fut/byocm_fut.ml b/code/src/byocm_fut/byocm_fut.ml --- a/code/src/byocm_fut/byocm_fut.ml +++ b/code/src/byocm_fut/byocm_fut.ml @@ -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 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 @@ 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 @@ 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 diff --git a/code/src/byocm_fut/byocm_fut.mli b/code/src/byocm_fut/byocm_fut.mli --- a/code/src/byocm_fut/byocm_fut.mli +++ b/code/src/byocm_fut/byocm_fut.mli @@ -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 diff --git a/presentation/byocm.tex b/presentation/byocm.tex --- a/presentation/byocm.tex +++ b/presentation/byocm.tex @@ -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}