diff --git a/examples/README.md b/examples/README.md index edd719a..c047611 100644 --- a/examples/README.md +++ b/examples/README.md @@ -10,7 +10,8 @@ $ dune build After successfully building the suite you can run each example via `dune`, which will run either the native or bytecode version of an -example depending on which suffix you supply, e.g. to run the native version type +example depending on which suffix you supply, e.g. to run the native +version type ```shell $ dune exec ./nqueens.exe diff --git a/examples/async.ml b/examples/async.ml index 38605da..b6c9280 100644 --- a/examples/async.ml +++ b/examples/async.ml @@ -81,31 +81,27 @@ end = struct if Queue.is_empty st.suspended then () else Queue.take st.suspended () - let hsched : unit -> (unit, unit) Effect.Deep.handler - = fun () -> + let run : (unit -> unit) -> unit + = fun f -> let state = { suspended = Queue.create () } in - let open Effect.Deep in - { retc = (fun () -> run_next state) - ; exnc = (fun e -> - match e with - | End_of_strand -> run_next state - | e -> raise e) - ; effc = (fun (type a) (eff : a Effect.t) -> - match eff with - | Await pr -> Some (fun (k : (a, unit) continuation) -> - (if Promise.is_done pr - then continue k (Promise.value pr) - else Promise.wait pr (fun v -> continue k v)); - run_next state) - | Fork -> Some (fun (k : (bool, unit) continuation) -> - let open Multicont.Deep in - let r = promote k in - enqueue state (fun () -> resume r false); - resume r true) - | Yield -> Some (fun (k : (unit, unit) continuation) -> - enqueue state (fun () -> continue k ()); - run_next state) - | _ -> None) } + match f () with + | () -> () + | exception End_of_strand -> run_next state + | effect Await pr, k -> + let open Effect.Deep in + (if Promise.is_done pr + then continue k (Promise.value pr) + else Promise.wait pr (fun v -> continue k v)); + run_next state + | effect Fork, k -> + let open Multicont.Deep in + let r = promote k in + enqueue state (fun () -> resume r false); + resume r true + | effect Yield, k -> + let open Effect.Deep in + enqueue state (fun () -> continue k ()); + run_next state end let run : (unit -> 'a) -> 'a @@ -115,7 +111,7 @@ end = struct let v = f () in result := (fun () -> v) in - let () = Effect.Deep.match_with f' () (Scheduler.hsched ()) in + Scheduler.run f'; !result () end @@ -128,17 +124,9 @@ module Env = struct let bind : int -> (unit -> 'b) -> 'b = fun v f -> - let open Effect.Deep in - let hdynbind : ('b, 'b) Effect.Deep.handler = - { retc = (fun x -> x) - ; exnc = (fun e -> raise e) - ; effc = (fun (type a) (eff : a Effect.t) -> - match eff with - | Ask -> Some (fun (k : (a, _) continuation) -> - continue k v) - | _ -> None) } - in - match_with f () hdynbind + match f () with + | ans -> ans + | effect Ask, k -> Effect.Deep.continue k v end (* The `well-behaveness' of this implementation can be illustrated by diff --git a/examples/choice.ml b/examples/choice.ml index a7d3f5d..8463732 100644 --- a/examples/choice.ml +++ b/examples/choice.ml @@ -2,8 +2,6 @@ * McCarthy's locally angelic choice *) -open Effect.Deep - type 'a Effect.t += Choose : (unit -> 'a) list -> 'a Effect.t let amb : (unit -> 'a) list -> 'a @@ -27,20 +25,12 @@ let handle : (unit -> 'a) -> 'a = fun m -> (* McCarthy's locally angelic choice operator (angelic modulo nontermination). *) - let hamb = - { retc = (fun x -> x) - ; exnc = (fun e -> raise e) - ; effc = (fun (type b) (eff : b Effect.t) -> - match eff with - | Choose xs -> - Some - (fun (k : (b, _) continuation) -> - let open Multicont.Deep in - let r = promote k in - first_success (resume r) xs) - | _ -> None) } - in - match_with m () hamb + match m () with + | ans -> ans + | effect Choose xs, k -> + let open Multicont.Deep in + let r = promote k in + first_success (resume r) xs (* The following examples are adapted from Oleg Kiselyov "Non-deterministic choice amb" diff --git a/examples/generic_count.ml b/examples/generic_count.ml index 226374f..a147159 100644 --- a/examples/generic_count.ml +++ b/examples/generic_count.ml @@ -1,7 +1,5 @@ (* Generic counting example based on Hillerström et al. (2020) https://arxiv.org/abs/2007.00605 *) -open Effect.Deep - type _ Effect.t += Branch : bool Effect.t type point = int -> bool @@ -16,21 +14,18 @@ let xor_predicate : int -> predicate | [] -> false | v :: vs -> List.fold_left xor v vs -let generic_count : (bool, int) handler = - { retc = (fun x -> if x then 1 else 0) - ; exnc = (fun e -> raise e) - ; effc = (fun (type a) (eff : a Effect.t) -> - match eff with - | Branch -> - Some (fun (k : (a, _) continuation) -> - let open Multicont.Deep in - let r = promote k in - let tt = resume r true in - let ff = resume r false in - tt + ff) - | _ -> None) } +let generic_count : ((int -> bool) -> bool) -> int + = fun f -> + match f (fun _ -> Effect.perform Branch) with + | ans -> if ans then 1 else 0 + | effect Branch, k -> + let open Multicont.Deep in + let r = promote k in + let tt = resume r true in + let ff = resume r false in + tt + ff let _ = let n = try int_of_string Sys.argv.(1) with _ -> 8 in - let solutions = match_with (xor_predicate n) (fun _ -> Effect.perform Branch) generic_count in + let solutions = generic_count (xor_predicate n) in Printf.printf "%d\n" solutions diff --git a/examples/legacy/README.md b/examples/legacy/README.md new file mode 100644 index 0000000..2e1e739 --- /dev/null +++ b/examples/legacy/README.md @@ -0,0 +1,25 @@ +# Compiling and running the legacy examples + +The legacy examples do not make use of the effect handler syntax added +in OCaml 5.3. To compile and run the examples suite you must first +have installed the library via OPAM. In order to build the suite +simply invoke `dune`, i.e. + +```shell +$ dune build +``` + +After successfully building the suite you can run each example via +`dune`, which will run either the native or bytecode version of an +example depending on which suffix you supply, e.g. to run the native +version type + +```shell +$ dune exec ./nqueens.exe +``` + +and for the bytecode version type + +```shell +$ dune exec ./nqueens.bc.exe +``` \ No newline at end of file diff --git a/examples/legacy/async.ml b/examples/legacy/async.ml new file mode 100644 index 0000000..38605da --- /dev/null +++ b/examples/legacy/async.ml @@ -0,0 +1,187 @@ +(* An algebraically well-behaved implementation of async/await with + multi-shot continuations. *) + +module Async: sig + module Promise: sig + type 'a t + exception Circular_await + end + + val await : 'a Promise.t -> 'a + val async : (unit -> 'a) -> 'a Promise.t + val yield : unit -> unit + val run : (unit -> 'a) -> 'a +end = struct + + module Promise = struct + type 'a promise = Done of 'a + | Pending of ('a -> unit) list + type 'a t = 'a promise ref + + exception Circular_await + + let is_done : 'a t -> bool + = fun pr -> match !pr with + | Done _ -> true + | _ -> false + + let wait : 'a t -> ('a -> unit) -> unit + = fun pr r -> match !pr with + | Done _ -> assert false + | Pending rs -> pr := Pending (r :: rs) + + let value : 'a t -> 'a + = fun pr -> match !pr with + | Done v -> v + | Pending _ -> assert false + + let make_empty : unit -> 'a t + = fun () -> ref (Pending []) + end + + type _ Effect.t += Await : 'a Promise.t -> 'a Effect.t + | Fork : bool Effect.t + | Yield : unit Effect.t + + + exception End_of_strand + + let await : 'a Promise.t -> 'a + = fun pr -> Effect.perform (Await pr) + + let fork : unit -> bool + = fun () -> Effect.perform Fork + + let yield : unit -> unit + = fun () -> Effect.perform Yield + + let async : (unit -> 'a) -> 'a Promise.t + = fun f -> + let pr = Promise.make_empty () in + if fork () (* returns twice *) + then pr + else let v = f () in + (match !pr with + | Done _ -> assert false + | Pending rs -> + pr := Done v; + List.iter (fun r -> r v) rs); + raise End_of_strand + + module Scheduler = struct + + type state = { suspended: (unit -> unit) Queue.t } + + let enqueue : state -> (unit -> unit) -> unit + = fun st r -> + Queue.add r st.suspended + + let run_next : state -> unit + = fun st -> + if Queue.is_empty st.suspended then () + else Queue.take st.suspended () + + let hsched : unit -> (unit, unit) Effect.Deep.handler + = fun () -> + let state = { suspended = Queue.create () } in + let open Effect.Deep in + { retc = (fun () -> run_next state) + ; exnc = (fun e -> + match e with + | End_of_strand -> run_next state + | e -> raise e) + ; effc = (fun (type a) (eff : a Effect.t) -> + match eff with + | Await pr -> Some (fun (k : (a, unit) continuation) -> + (if Promise.is_done pr + then continue k (Promise.value pr) + else Promise.wait pr (fun v -> continue k v)); + run_next state) + | Fork -> Some (fun (k : (bool, unit) continuation) -> + let open Multicont.Deep in + let r = promote k in + enqueue state (fun () -> resume r false); + resume r true) + | Yield -> Some (fun (k : (unit, unit) continuation) -> + enqueue state (fun () -> continue k ()); + run_next state) + | _ -> None) } + end + + let run : (unit -> 'a) -> 'a + = fun f -> + let result = ref (fun () -> raise Promise.Circular_await) in + let f' () = + let v = f () in + result := (fun () -> v) + in + let () = Effect.Deep.match_with f' () (Scheduler.hsched ()) in + !result () +end + +(* Another effect: dynamic binding *) +module Env = struct + type _ Effect.t += Ask : int Effect.t + + let ask : unit -> int + = fun () -> Effect.perform Ask + + let bind : int -> (unit -> 'b) -> 'b + = fun v f -> + let open Effect.Deep in + let hdynbind : ('b, 'b) Effect.Deep.handler = + { retc = (fun x -> x) + ; exnc = (fun e -> raise e) + ; effc = (fun (type a) (eff : a Effect.t) -> + match eff with + | Ask -> Some (fun (k : (a, _) continuation) -> + continue k v) + | _ -> None) } + in + match_with f () hdynbind +end + +(* The `well-behaveness' of this implementation can be illustrated by + using it in conjunction with another effect. In each async strand + any occurrence of `Ask' is correctly bound by an ambient + `Env.bind'. *) +let main () = + let task name () = + Printf.printf "starting %s\n%!" name; + let v = Env.ask () in + Printf.printf "yielding %s\n%!" name; + Async.yield (); + Printf.printf "ending %s with %d\n%!" name v; + v + in + let pa = + Env.bind 40 + (fun () -> Async.async (task "a")) + in + let pb = + Env.bind 2 + (fun () -> Async.async (task "b")) + in + let pc = + Async.async + (fun () -> Async.await pa + Async.await pb) + in + Printf.printf "Sum is %d\n" (Async.await pc); + assert Async.(await pa + await pb = await pc) + +let _ = Async.run main + +(* The following program would deadlock if cyclic + promise resolution was allowed *) +(* let try_deadlock () = + * let pr = ref (fun () -> assert false) in + * let task () = + * Async.await (!pr ()) + * in + * print_endline "Fork task"; + * let pr' = Async.async task in + * pr := (fun () -> pr'); + * print_endline "Await"; + * Async.await (!pr ()) + * + * let _ = Async.run try_deadlock *) diff --git a/examples/legacy/choice.ml b/examples/legacy/choice.ml new file mode 100644 index 0000000..a7d3f5d --- /dev/null +++ b/examples/legacy/choice.ml @@ -0,0 +1,74 @@ +(** + * McCarthy's locally angelic choice + *) + +open Effect.Deep + +type 'a Effect.t += Choose : (unit -> 'a) list -> 'a Effect.t + +let amb : (unit -> 'a) list -> 'a + = fun xs -> Effect.perform (Choose xs) + +let first_success (type b) : ('a -> b) -> (unit -> 'a) list -> b + = fun f gs -> + let exception Success of b in + try + List.iter + (fun g -> + try + let x = g () in + raise (Success (f x)) + with (Success _) as e -> raise e + | _ -> ()) + gs; raise (Failure "no success") + with Success r -> r + +let handle : (unit -> 'a) -> 'a + = fun m -> + (* McCarthy's locally angelic choice operator (angelic modulo + nontermination). *) + let hamb = + { retc = (fun x -> x) + ; exnc = (fun e -> raise e) + ; effc = (fun (type b) (eff : b Effect.t) -> + match eff with + | Choose xs -> + Some + (fun (k : (b, _) continuation) -> + let open Multicont.Deep in + let r = promote k in + first_success (resume r) xs) + | _ -> None) } + in + match_with m () hamb + +(* The following examples are adapted from Oleg Kiselyov + "Non-deterministic choice amb" + (c.f. https://okmij.org/ftp/ML/ML.html#amb) *) + +(* An angelic choice *always* picks the successful branch. *) +let _branch_example : unit -> int + = fun () -> + handle (fun () -> + if amb [(fun () -> true); (fun () -> false)] + then failwith "Fail" + else 42) + +(* More involved example, requiring `amb` to make three correct + choices. *) +let pyth : int list -> (int * int * int) + = fun numbers -> + let numbers' = List.map (fun n -> (fun () -> n)) numbers in + handle (fun () -> + let i = amb numbers' in + let j = amb numbers' in + let k = amb numbers' in + if i*i + j*j = k*k + then (i, j, k) + else failwith "no solution") + +let pyth_example () = pyth [1;2;3;4;5] + +let _ = + let (x, y, z) = pyth_example () in + Printf.printf "(%d, %d, %d)\n%!" x y z diff --git a/examples/legacy/dune b/examples/legacy/dune new file mode 100644 index 0000000..6d4bbc2 --- /dev/null +++ b/examples/legacy/dune @@ -0,0 +1,41 @@ +(executable + (name nqueens) + (modules nqueens) + (modes byte_complete native) + (libraries multicont)) + +(executable + (name generic_count) + (modules generic_count) + (modes byte_complete native) + (libraries multicont)) + +(executable + (name choice) + (modules choice) + (modes byte_complete native) + (libraries multicont)) + +(executable + (name rollback) + (modules rollback) + (modes byte_complete native) + (libraries multicont unix)) + +(executable + (name supervised) + (modules supervised) + (modes byte_complete native) + (libraries multicont)) + +(executable + (name async) + (modules async) + (modes byte_complete native) + (libraries multicont)) + +(executable + (name return) + (modules return) + (modes byte_complete native) + (libraries multicont)) diff --git a/examples/legacy/generic_count.ml b/examples/legacy/generic_count.ml new file mode 100644 index 0000000..226374f --- /dev/null +++ b/examples/legacy/generic_count.ml @@ -0,0 +1,36 @@ +(* Generic counting example based on Hillerström et al. (2020) https://arxiv.org/abs/2007.00605 *) + +open Effect.Deep + +type _ Effect.t += Branch : bool Effect.t + +type point = int -> bool +type predicate = point -> bool + +let xor : bool -> bool -> bool + = fun p q -> (p || q) && not (p && q) + +let xor_predicate : int -> predicate + = fun n p -> + match List.init n p with + | [] -> false + | v :: vs -> List.fold_left xor v vs + +let generic_count : (bool, int) handler = + { retc = (fun x -> if x then 1 else 0) + ; exnc = (fun e -> raise e) + ; effc = (fun (type a) (eff : a Effect.t) -> + match eff with + | Branch -> + Some (fun (k : (a, _) continuation) -> + let open Multicont.Deep in + let r = promote k in + let tt = resume r true in + let ff = resume r false in + tt + ff) + | _ -> None) } + +let _ = + let n = try int_of_string Sys.argv.(1) with _ -> 8 in + let solutions = match_with (xor_predicate n) (fun _ -> Effect.perform Branch) generic_count in + Printf.printf "%d\n" solutions diff --git a/examples/legacy/nqueens.ml b/examples/legacy/nqueens.ml new file mode 100644 index 0000000..2f6775b --- /dev/null +++ b/examples/legacy/nqueens.ml @@ -0,0 +1,57 @@ +(* Prints the number of solutions to a given n-Queens problem. + + Adapted from + https://github.com/effect-handlers/effect-handlers-bench/blob/ca4ed12fc2265c16c562016ec09f0466d81d1ddd/benchmarks/ocaml/001_nqueens/001_nqueens_ocaml.ml + *) + +open Effect.Deep + +let n = try int_of_string Sys.argv.(1) with _ -> 8 + +(* n-Queens logic. *) +let rec safe queen diag xs = + match xs with + | [] -> true + | q :: qs -> queen <> q && queen <> q + diag && queen <> q - diag && + safe queen (diag + 1) qs + +type _ Effect.t += Pick : int -> int Effect.t +exception Fail + +let rec find_solution n col : int list = + if col = 0 then [] + else let sol = find_solution n (col - 1) in + let queen = Effect.perform (Pick n) in + if safe queen 1 sol then queen::sol else raise Fail + +(* Deep effect handler that counts the number of solutions to an + n-Queens problem. *) +let count_queens_solutions = + { retc = (fun _ -> 1) (* If the computation returns, then we have found a solution. *) + ; exnc = (fun e -> match e with Fail -> 0 | _ -> raise e) (* If the computation fails, then we have not found a solution. *) + ; effc = (fun (type a) (eff : a Effect.t) -> + match eff with + | Pick n -> (* We handle [Pick] by successively trying to place + Queens on the board by invoking the provided + continuation with different values. Each invocation + returns the number of solutions in the + subcomputation. *) + Some (fun (k : (a, _) continuation) -> + let open Multicont.Deep in + (* Convert [k] into a multi-shot resumption *) + let r = promote k in + let rec loop i acc = + if i > n then acc + else (* Invoke the resumption. This branch may be + executed many times. *) + let nsol = resume r i in + loop (i + 1) (nsol + acc) + in + loop 1 0) + | _ -> None) } + +let queens_count n = + match_with (fun () -> find_solution n n) () count_queens_solutions + +let _ = + Printf.printf "%d\n" (queens_count n) diff --git a/examples/legacy/return.ml b/examples/legacy/return.ml new file mode 100644 index 0000000..91584ec --- /dev/null +++ b/examples/legacy/return.ml @@ -0,0 +1,102 @@ +(* Illustrating the `return' operator as an effect. + * + * This encoding utilises only a single handler by leveraging the + * power of multi-shot continuations to fork two strands of + * computations. We use the first strand as the context wherein we + * evaluate the function, whilst we use the second strand as a sort of + * identity context wherein we return the value of the function + * application without applying the function. In terms of effects, + * exceptions, and handlers, we are going to use one operations: Fork + * : () => [|Apply|Done:a|] which forks the current context, the + * return value signals which strand of computation to execute; and + * one exception Return of a. Concretely the idea is to invoke Fork + * before a function application to capture the current continuation + * of the application (i.e. the return point). Inside the handler for + * Fork and Return we maintain a stack of continuations arising from + * invocations of Fork. + *) + +module type ALG = sig + type t + val apply : (t -> t) -> t -> t + val return : t -> 'a + val toplevel : (unit -> t) -> t +end + +module Alg(D : sig type t end) : ALG with type t := D.t = struct + type t = D.t + + type cmd = Apply + | Done of t + exception Return of t + type _ Effect.t += Fork : cmd Effect.t + + let return x = raise (Return x) + + let apply f x = + match Effect.perform Fork with + | Apply -> return (f x) + | Done ans -> ans + + let htoplevel : unit -> (t, t) Effect.Deep.handler + = fun () -> + let open Effect.Deep in + let open Multicont.Deep in + let conts = ref [] in + let backup ans = + match !conts with + | r :: conts' -> + conts := conts'; + resume r (Done ans) + | _ -> ans + in + let push r = + conts := r :: !conts + in + { retc = (fun ans -> ans) + ; exnc = + (function + Return ans -> backup ans + | e -> raise e) + ; effc = (fun (type a) (eff : a Effect.t) -> + match eff with + | Fork -> + Some (fun (k : (a, _) continuation) -> + let r = promote k in + push r; + resume r Apply) + | _ -> None) } + + let toplevel f = + Effect.Deep.match_with f () (htoplevel ()) +end + +let fac n = + let module I = Alg(struct type t = int end) in + let rec fac n = + if n = 0 then 1 + else n * (I.apply fac (n - 1)) + in + let negate x = + I.apply ((-) 0) x + in + I.toplevel + (fun () -> + I.apply negate (I.apply fac n)) + +let fac' n = + let module I = Alg(struct type t = int end) in + let rec fac n = + if n = 0 then I.return 1; + n * (I.apply fac (n - 1)) + in + let negate x = + I.apply ((-) 0) x + in + I.toplevel + (fun () -> + I.apply negate (I.apply fac n)) + +let _ = + Printf.printf "%d\n%!" (fac 7); + Printf.printf "%d\n%!" (fac' 7); diff --git a/examples/legacy/rollback.ml b/examples/legacy/rollback.ml new file mode 100644 index 0000000..040b220 --- /dev/null +++ b/examples/legacy/rollback.ml @@ -0,0 +1,120 @@ +(* Modular rollback parsing. Adapted from Lindley et al. (2017), + c.f. https://arxiv.org/pdf/1611.09259.pdf *) + +module IO = struct + let term_io = Unix.(tcgetattr stdin) + + let get_char () = + (* Disable canonical processing and echoing of input + characters. *) + Unix.(tcsetattr + stdin + TCSADRAIN + { term_io with c_icanon = false; c_echo = false }); + let ch = input_char stdin in + (* Restore terminal defaults. *) + Unix.(tcsetattr stdin TCSADRAIN term_io); + ch + + let put_char ch = + output_char stdout ch; flush stdout +end + +type _ Effect.t += Peek : (unit -> char) Effect.t (* Returning a thunk is necessary to avoid a memory leak. See below. *) + | Accept : unit Effect.t + +exception Abort + +let peek : unit -> char + = fun () -> (Effect.perform Peek) () + +let accept : unit -> unit + = fun () -> Effect.perform Accept + +let abort : unit -> 'a + = fun () -> raise Abort + +type 'a log = Start of (unit, 'a) Multicont.Shallow.resumption + | Inched of 'a log * ((unit -> char), 'a) Multicont.Shallow.resumption + | Ouched of 'a log + + +(* let identity : ('a, 'a) Effect.Shallow.handler + * = { retc = (fun x -> x) + * ; exnc = (fun e -> raise e) + * ; effc = (fun (type a) (_ : a Effect.t) -> None) } *) + +let rec input : 'a log -> char option -> ('a, 'a) Effect.Shallow.handler + = fun l buf -> + let open Effect.Shallow in + { retc = (fun x -> x) + ; exnc = (function Abort -> rollback l | e -> raise e) + ; effc = (fun (type a) (eff : a Effect.t) -> + match eff with + | Peek -> Some (fun (k : (a, _) continuation) -> + let open Multicont.Shallow in + let r = promote k in + match buf with + | Some c -> resume_with r (fun () -> c) (input l buf) + | None -> match IO.get_char () with + | '\b' -> rollback l + | c -> resume_with r (fun () -> c) (input (Inched (l, r)) (Some c))) + | Accept -> Some (fun (k : (a, _) continuation) -> + let open Multicont.Shallow in + let r = promote k in + match buf with + | Some c -> IO.put_char c; + resume_with r () (input (Ouched l) None) + | None -> resume_with r () (input l None)) + | _ -> None) } +and rollback : 'a log -> 'a = function + | Start p -> parse p + | Ouched l -> IO.put_char '\b'; + rollback l + | Inched (l, r) -> + (* Here we want to inject a computation into the + continuation. Specifically, we want to run the `peek` + computation at the suspension point. For this reason the + operation `Peek` returns a thunk of type `unit -> + char`. Alternatively, we could wrap the composition `peek (); + resume_with r (Input l None)` in an identity handler. Though, + this introduces to a memory leak.*) + let open Multicont.Shallow in + resume_with r peek (input l None) +and parse : (unit, 'a) Multicont.Shallow.resumption -> 'a + = fun r -> + let open Multicont.Shallow in + resume_with r () (input (Start r) None) + +let rec zeros : int -> int + = fun n -> + match peek () with + | '0' -> accept (); zeros (n+1) + | ' ' -> accept (); n + | _ -> abort () + +let _t1 () = + let open Effect.Shallow in + let open Multicont.Shallow in + let i = parse (promote (fiber (fun () -> zeros 0))) in + Printf.printf "%d\n%!" i + +let rec nest : char list -> int -> char list + = fun cs n -> + if n = 0 + then match peek () with + | '(' -> accept (); nest cs 1 + | '\n' -> accept (); cs + | _ -> abort () + else match peek () with + | '(' -> accept (); nest cs (n + 1) + | ')' -> accept (); nest cs (n - 1) + | c -> accept (); nest (c :: cs) n + +let t2 () = + let open Effect.Shallow in + let open Multicont.Shallow in + let cs = List.rev (parse (promote (fiber (fun () -> nest [] 0)))) in + Printf.printf "%s\n" (String.init (List.length cs) (fun i -> List.nth cs i)) + +let _ = t2 () diff --git a/examples/legacy/supervised.ml b/examples/legacy/supervised.ml new file mode 100644 index 0000000..2e7702c --- /dev/null +++ b/examples/legacy/supervised.ml @@ -0,0 +1,137 @@ +(* Restartable processes *) + +module Pid = struct + type t = Zero + | NonZero of int + + let is_zero : t -> bool = function + | Zero -> true + | _ -> false + + let zero : t = Zero + + let make : int -> t + = fun ident -> NonZero ident +end + +type _ Effect.t += Fork : Pid.t Effect.t + | Join : Pid.t -> unit Effect.t + +exception Fail + + +let fork : unit -> Pid.t + = fun () -> Effect.perform Fork + +let join : Pid.t -> unit + = fun pid -> Effect.perform (Join pid) + +let fail : unit -> 'a + = fun () -> raise Fail + + +(* Supervisor state *) +type sstate = { mutable suspended: (Pid.t * (unit -> unit)) list + ; mutable blocked: (Pid.t * (Pid.t * (unit -> unit)) list) list + ; mutable finished: Pid.t list + ; mutable active: Pid.t * (unit -> unit) + ; mutable nextpid: int } + +let supervise : (unit -> unit) -> unit + = fun f -> + let state = + { suspended = [] + ; blocked = [] + ; finished = [] + ; active = (Pid.zero, (fun () -> assert false)) + ; nextpid = 2 } + in + let supervisor = + let open Effect.Deep in + { retc = (fun _ -> + let (pid, _) = state.active in + state.finished <- pid :: state.finished; + let rs, blocked = + match List.assoc_opt pid state.blocked with + | None -> [], state.blocked + | Some rs -> rs, List.remove_assoc pid state.blocked + in + match state.suspended @ rs with + | [] -> () + | (pid, r) :: rs -> + state.suspended <- rs; + state.blocked <- blocked; + state.active <- (pid, r); + r ()) + ; exnc = (fun e -> + match e with + | Fail -> + begin match state.suspended @ [state.active] with + | [] -> assert false + | (pid, r) :: rs -> + state.active <- (pid, r); + state.suspended <- rs; + r () + end + | _ -> raise e) + ; effc = (fun (type a) (eff : a Effect.t) -> + match eff with + | Fork -> + Some (fun (k : (a, _) continuation) -> + let open Multicont.Deep in + let r = promote k in + let pid = + let i = state.nextpid in + state.nextpid <- i + 1; + Pid.make i + in + state.suspended <- state.suspended @ [pid, (fun () -> resume r Pid.zero)]; + resume r pid) + | Join (pid : Pid.t) -> + Some (fun (k : (a, _) continuation) -> + let open Multicont.Deep in + let r = promote k in + if List.mem pid state.finished + then resume r () + else let blocked = + match List.assoc_opt pid state.blocked with + | None -> (pid, [fst state.active, (fun () -> resume r ())]) :: state.blocked + | Some _ -> state.blocked + in + state.blocked <- blocked; + match state.suspended with + | [] -> assert false + | (pid', r) :: rs -> + state.active <- (pid', r); + state.suspended <- rs; + r ()) + | _ -> None) } + in + state.active <- (Pid.make 1, (fun () -> Effect.Deep.match_with f () supervisor)); + Effect.Deep.match_with f () supervisor + +let child : int -> int -> int ref -> unit + = fun i n st -> + if !st < n + then (incr st + ; Printf.printf "Child %d failed!\n%!" i + ; fail ()) + else Printf.printf "Child %d succeeded!\n%!" i + +let example () = + let s = ref 0 in + let pid = fork () in + if Pid.is_zero pid + then let pid = fork () in + if Pid.is_zero pid + then child 2 5 s + else (child 1 3 s + ; Printf.printf "Child 1 joining with Child 2\n%!" + ; join pid + ; Printf.printf "Child 1 joined with Child 2\n%!") + else (print_endline "Parent joining with Child 1" + ; join pid + ; print_endline "Parent joined with Child 1") + +let _ = supervise example + diff --git a/examples/nqueens.ml b/examples/nqueens.ml index 2f6775b..64b1708 100644 --- a/examples/nqueens.ml +++ b/examples/nqueens.ml @@ -4,8 +4,6 @@ https://github.com/effect-handlers/effect-handlers-bench/blob/ca4ed12fc2265c16c562016ec09f0466d81d1ddd/benchmarks/ocaml/001_nqueens/001_nqueens_ocaml.ml *) -open Effect.Deep - let n = try int_of_string Sys.argv.(1) with _ -> 8 (* n-Queens logic. *) @@ -26,32 +24,26 @@ let rec find_solution n col : int list = (* Deep effect handler that counts the number of solutions to an n-Queens problem. *) -let count_queens_solutions = - { retc = (fun _ -> 1) (* If the computation returns, then we have found a solution. *) - ; exnc = (fun e -> match e with Fail -> 0 | _ -> raise e) (* If the computation fails, then we have not found a solution. *) - ; effc = (fun (type a) (eff : a Effect.t) -> - match eff with - | Pick n -> (* We handle [Pick] by successively trying to place - Queens on the board by invoking the provided - continuation with different values. Each invocation - returns the number of solutions in the - subcomputation. *) - Some (fun (k : (a, _) continuation) -> - let open Multicont.Deep in - (* Convert [k] into a multi-shot resumption *) - let r = promote k in - let rec loop i acc = - if i > n then acc - else (* Invoke the resumption. This branch may be - executed many times. *) - let nsol = resume r i in - loop (i + 1) (nsol + acc) - in - loop 1 0) - | _ -> None) } - let queens_count n = - match_with (fun () -> find_solution n n) () count_queens_solutions + match find_solution n n with + | _ -> 1 (* If the computation returns, then we have found a solution. *) + | exception Fail -> 0 (* If the computation fails, then we have not found a solution. *) + | effect Pick n, k -> + (* We handle [Pick] by successively trying to place Queens on the + board by invoking the provided continuation with different + values. Each invocation returns the number of solutions in the + subcomputation. *) + let open Multicont.Deep in + (* Convert [k] into a multi-shot resumption *) + let r = promote k in + let rec loop i acc = + if i > n then acc + else (* Invoke the resumption. This branch may be executed many + times. *) + let nsol = resume r i in + loop (i + 1) (nsol + acc) + in + loop 1 0 let _ = Printf.printf "%d\n" (queens_count n) diff --git a/examples/return.ml b/examples/return.ml index 91584ec..63eae53 100644 --- a/examples/return.ml +++ b/examples/return.ml @@ -38,9 +38,8 @@ module Alg(D : sig type t end) : ALG with type t := D.t = struct | Apply -> return (f x) | Done ans -> ans - let htoplevel : unit -> (t, t) Effect.Deep.handler - = fun () -> - let open Effect.Deep in + let toplevel : (unit -> t) -> t + = fun f -> let open Multicont.Deep in let conts = ref [] in let backup ans = @@ -53,22 +52,13 @@ module Alg(D : sig type t end) : ALG with type t := D.t = struct let push r = conts := r :: !conts in - { retc = (fun ans -> ans) - ; exnc = - (function - Return ans -> backup ans - | e -> raise e) - ; effc = (fun (type a) (eff : a Effect.t) -> - match eff with - | Fork -> - Some (fun (k : (a, _) continuation) -> - let r = promote k in - push r; - resume r Apply) - | _ -> None) } - - let toplevel f = - Effect.Deep.match_with f () (htoplevel ()) + match f () with + | ans -> ans + | exception Return ans -> backup ans + | effect Fork, k -> + let r = promote k in + push r; + resume r Apply end let fac n = diff --git a/examples/supervised.ml b/examples/supervised.ml index 2e7702c..c61e214 100644 --- a/examples/supervised.ml +++ b/examples/supervised.ml @@ -43,72 +43,61 @@ let supervise : (unit -> unit) -> unit { suspended = [] ; blocked = [] ; finished = [] - ; active = (Pid.zero, (fun () -> assert false)) + ; active = (Pid.make 1, (fun () -> assert false)) ; nextpid = 2 } in - let supervisor = - let open Effect.Deep in - { retc = (fun _ -> - let (pid, _) = state.active in - state.finished <- pid :: state.finished; - let rs, blocked = - match List.assoc_opt pid state.blocked with - | None -> [], state.blocked - | Some rs -> rs, List.remove_assoc pid state.blocked - in - match state.suspended @ rs with - | [] -> () - | (pid, r) :: rs -> - state.suspended <- rs; - state.blocked <- blocked; - state.active <- (pid, r); - r ()) - ; exnc = (fun e -> - match e with - | Fail -> - begin match state.suspended @ [state.active] with - | [] -> assert false - | (pid, r) :: rs -> - state.active <- (pid, r); - state.suspended <- rs; - r () - end - | _ -> raise e) - ; effc = (fun (type a) (eff : a Effect.t) -> - match eff with - | Fork -> - Some (fun (k : (a, _) continuation) -> - let open Multicont.Deep in - let r = promote k in - let pid = - let i = state.nextpid in - state.nextpid <- i + 1; - Pid.make i - in - state.suspended <- state.suspended @ [pid, (fun () -> resume r Pid.zero)]; - resume r pid) - | Join (pid : Pid.t) -> - Some (fun (k : (a, _) continuation) -> - let open Multicont.Deep in - let r = promote k in - if List.mem pid state.finished - then resume r () - else let blocked = - match List.assoc_opt pid state.blocked with - | None -> (pid, [fst state.active, (fun () -> resume r ())]) :: state.blocked - | Some _ -> state.blocked - in - state.blocked <- blocked; - match state.suspended with - | [] -> assert false - | (pid', r) :: rs -> - state.active <- (pid', r); - state.suspended <- rs; - r ()) - | _ -> None) } - in - state.active <- (Pid.make 1, (fun () -> Effect.Deep.match_with f () supervisor)); - Effect.Deep.match_with f () supervisor + match f () with + | () -> + let (pid, _) = state.active in + state.finished <- pid :: state.finished; + let rs, blocked = + match List.assoc_opt pid state.blocked with + | None -> [], state.blocked + | Some rs -> rs, List.remove_assoc pid state.blocked + in + begin match state.suspended @ rs with + | [] -> () + | (pid, r) :: rs -> + state.suspended <- rs; + state.blocked <- blocked; + state.active <- (pid, r); + r () + end + | exception Fail -> + begin match state.suspended @ [state.active] with + | [] -> assert false + | (pid, r) :: rs -> + state.active <- (pid, r); + state.suspended <- rs; + r () + end + | effect Fork, k -> + let open Multicont.Deep in + let r = promote k in + let pid = + let i = state.nextpid in + state.nextpid <- i + 1; + Pid.make i + in + state.suspended <- state.suspended @ [pid, (fun () -> resume r Pid.zero)]; + resume r pid + | effect Join pid, k -> + let open Multicont.Deep in + let r = promote k in + if List.mem pid state.finished + then resume r () + else let blocked = + match List.assoc_opt pid state.blocked with + | None -> (pid, [fst state.active, (fun () -> resume r ())]) :: state.blocked + | Some _ -> state.blocked + in + state.blocked <- blocked; + match state.suspended with + | [] -> assert false + | (pid', r) :: rs -> + state.active <- (pid', r); + state.suspended <- rs; + r () let child : int -> int -> int ref -> unit = fun i n st -> diff --git a/test/dune b/test/dune index 2ac8d65..12bbb57 100644 --- a/test/dune +++ b/test/dune @@ -3,6 +3,6 @@ (targets tests.inc) (deps (:gen gen/testrules.exe)) (mode promote) - (action (run %{gen} -ocamlc %{ocamlc} -output %{targets}))) + (action (run %{gen} -ocamlc %{ocamlc} -ocaml_version %{ocaml_version} -output %{targets}))) (include tests.inc) diff --git a/test/gen/testrules.ml b/test/gen/testrules.ml index 53ee63a..9a9f23e 100644 --- a/test/gen/testrules.ml +++ b/test/gen/testrules.ml @@ -17,15 +17,18 @@ let detect_native_compiler ocamlc = List.exists (fun s -> String.equal s "native_compiler: true") lines with _ -> false -let make_diff_stanzas native testname = +let make_diff_stanzas is_version_53 native testname = let stanzas exe_prefix = let output = Printf.sprintf "(rule\n\ \ (with-stdout-to %s.output\n\ \ (setenv \"LD_LIBRARY_PATH\" \".\"\n\ - \ (run %s/examples/%s.exe))))" - exe_prefix "%{workspace_root}" exe_prefix + \ (run %s/examples/%s%s.exe))))" + exe_prefix + "%{workspace_root}" + (if is_version_53 then "" else "legacy/") + exe_prefix in let runtest = Printf.sprintf @@ -73,15 +76,21 @@ let _ = in let incfile = ref "tests.inc" in let is_native_available = ref false in + let is_version_53 = ref false in C.main ~name:"tests" ~args:[ "-ocamlc", Arg.String (fun ocamlc -> is_native_available := detect_native_compiler ocamlc), "Name of the ocamlc executable"; + "-ocaml_version", Arg.String (fun version -> + is_version_53 := String.length version >= 3 + && Char.compare (String.get version 0) '5' >= 0 + && Char.compare (String.get version 2) '3' >= 0), + "OCaml version"; "-output", Arg.String (fun s -> incfile := s), "Name for the tests sexp output (default tests.inc)" ] (fun _ -> - let diff_tests = List.map (make_diff_stanzas !is_native_available) diff_testnames in + let diff_tests = List.map (make_diff_stanzas !is_version_53 !is_native_available) diff_testnames in let nondiff_tests = List.map (make_nondiff_stanzas !is_native_available) nondiff_testnames in write_content !incfile (List.concat [List.concat diff_tests; List.concat nondiff_tests])) diff --git a/test/tests.inc b/test/tests.inc index f52a67b..52cac68 100644 --- a/test/tests.inc +++ b/test/tests.inc @@ -1 +1,128 @@ -; intentionally left empty \ No newline at end of file +; async tests +(rule + (with-stdout-to async.output + (setenv "LD_LIBRARY_PATH" "." + (run %{workspace_root}/examples/async.exe)))) + +(rule + (alias runtest) + (action (diff async.expected async.output))) + +(rule + (with-stdout-to async.bc.output + (setenv "LD_LIBRARY_PATH" "." + (run %{workspace_root}/examples/async.bc.exe)))) + +(rule + (alias runtest) + (action (diff async.expected async.bc.output))) + +; choice tests +(rule + (with-stdout-to choice.output + (setenv "LD_LIBRARY_PATH" "." + (run %{workspace_root}/examples/choice.exe)))) + +(rule + (alias runtest) + (action (diff choice.expected choice.output))) + +(rule + (with-stdout-to choice.bc.output + (setenv "LD_LIBRARY_PATH" "." + (run %{workspace_root}/examples/choice.bc.exe)))) + +(rule + (alias runtest) + (action (diff choice.expected choice.bc.output))) + +; generic_count tests +(rule + (with-stdout-to generic_count.output + (setenv "LD_LIBRARY_PATH" "." + (run %{workspace_root}/examples/generic_count.exe)))) + +(rule + (alias runtest) + (action (diff generic_count.expected generic_count.output))) + +(rule + (with-stdout-to generic_count.bc.output + (setenv "LD_LIBRARY_PATH" "." + (run %{workspace_root}/examples/generic_count.bc.exe)))) + +(rule + (alias runtest) + (action (diff generic_count.expected generic_count.bc.output))) + +; nqueens tests +(rule + (with-stdout-to nqueens.output + (setenv "LD_LIBRARY_PATH" "." + (run %{workspace_root}/examples/nqueens.exe)))) + +(rule + (alias runtest) + (action (diff nqueens.expected nqueens.output))) + +(rule + (with-stdout-to nqueens.bc.output + (setenv "LD_LIBRARY_PATH" "." + (run %{workspace_root}/examples/nqueens.bc.exe)))) + +(rule + (alias runtest) + (action (diff nqueens.expected nqueens.bc.output))) + +; return tests +(rule + (with-stdout-to return.output + (setenv "LD_LIBRARY_PATH" "." + (run %{workspace_root}/examples/return.exe)))) + +(rule + (alias runtest) + (action (diff return.expected return.output))) + +(rule + (with-stdout-to return.bc.output + (setenv "LD_LIBRARY_PATH" "." + (run %{workspace_root}/examples/return.bc.exe)))) + +(rule + (alias runtest) + (action (diff return.expected return.bc.output))) + +; supervised tests +(rule + (with-stdout-to supervised.output + (setenv "LD_LIBRARY_PATH" "." + (run %{workspace_root}/examples/supervised.exe)))) + +(rule + (alias runtest) + (action (diff supervised.expected supervised.output))) + +(rule + (with-stdout-to supervised.bc.output + (setenv "LD_LIBRARY_PATH" "." + (run %{workspace_root}/examples/supervised.bc.exe)))) + +(rule + (alias runtest) + (action (diff supervised.expected supervised.bc.output))) + +; unique_fibers tests +(rule + (alias runtest) + (action + (setenv "TEST_UNIQUE_FIBERS" "false" + (setenv "LD_LIBRARY_PATH" "." + (run %{workspace_root}/test/lib/unique_fibers.bc.exe))))) + +(rule + (alias runtest) + (action + (setenv "TEST_UNIQUE_FIBERS" "false" + (setenv "LD_LIBRARY_PATH" "." + (run %{workspace_root}/test/lib/unique_fibers.exe)))))