Skip to content

Commit 123f6b1

Browse files
authored
Merge pull request #42 from avsm/effect-syntax
Use effect syntax for OCaml 5.3
2 parents ac859f0 + d0ef043 commit 123f6b1

23 files changed

Lines changed: 422 additions & 464 deletions

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
- ubuntu-latest
1616
- macos-latest
1717
ocaml-compiler:
18-
- ocaml-base-compiler.5.1.0
18+
- ocaml-base-compiler.5.3.0+trunk
1919

2020
runs-on: ${{ matrix.os }}
2121

.ocamlformat

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
profile = default
2-
version = 0.26.0
2+
version = 0.26.2

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
EXE := concurrent.exe ref.exe transaction.exe echo.exe \
22
dyn_wind.exe generator.exe promises.exe reify_reflect.exe \
33
MVar_test.exe chameneos.exe eratosthenes.exe pipes.exe loop.exe \
4-
fringe.exe algorithmic_differentiation.exe
4+
fringe.exe algorithmic_differentiation.exe dynamic_state.exe
55

66
all: $(EXE)
77

README.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,15 @@ The original implementation of Multicore OCaml allowed a user to `Obj.clone_cont
3838

3939
## Running the examples
4040

41-
To run the examples with Multicore OCaml, be sure to install [Opam with these instructions](https://opam.ocaml.org/doc/Install.html). If your version of Opam (`opam --version`) is greater than or equal to `2.1` then the following instructions will work:
41+
To run the examples with OCaml, be sure to install [Opam with these instructions](https://opam.ocaml.org/doc/Install.html).
4242

4343
```bash
44-
# After cloning this repository, create a 5.1.0 switch
44+
# After cloning this repository, create a 5.1 switch
4545
opam update
46-
# Add the alpha repository to get unreleased 5.1.0 compatible libraries
47-
opam switch create 5.1.0
46+
opam switch create 5.1.1
4847
opam install . --deps-only
4948
```
5049

51-
5250
Running `make` will build all of the examples. If you want to run a single executable that is built with `dune` you can run:
5351

5452
```

algorithmic_differentiation.ml

Lines changed: 16 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -17,39 +17,24 @@ end = struct
1717

1818
let mk v = { v; d = 0.0 }
1919

20-
type _ Effect.t += Add : t * t -> t Effect.t
21-
type _ Effect.t += Mult : t * t -> t Effect.t
20+
type _ eff += Add : t * t -> t eff
21+
type _ eff += Mult : t * t -> t eff
2222

2323
let run f =
24-
ignore
25-
(match_with f ()
26-
{
27-
retc =
28-
(fun r ->
29-
r.d <- 1.0;
30-
r);
31-
exnc = raise;
32-
effc =
33-
(fun (type a) (e : a Effect.t) ->
34-
match e with
35-
| Add (a, b) ->
36-
Some
37-
(fun (k : (a, _) continuation) ->
38-
let x = { v = a.v +. b.v; d = 0.0 } in
39-
ignore (continue k x);
40-
a.d <- a.d +. x.d;
41-
b.d <- b.d +. x.d;
42-
x)
43-
| Mult (a, b) ->
44-
Some
45-
(fun k ->
46-
let x = { v = a.v *. b.v; d = 0.0 } in
47-
ignore (continue k x);
48-
a.d <- a.d +. (b.v *. x.d);
49-
b.d <- b.d +. (a.v *. x.d);
50-
x)
51-
| _ -> None);
52-
})
24+
ignore (match f () with
25+
| r -> r.d <- 1.0; r;
26+
| effect (Add(a,b)), k ->
27+
let x = {v = a.v +. b.v; d = 0.0} in
28+
ignore (continue k x);
29+
a.d <- a.d +. x.d;
30+
b.d <- b.d +. x.d;
31+
x
32+
| effect (Mult(a,b)), k ->
33+
let x = {v = a.v *. b.v; d = 0.0} in
34+
ignore (continue k x);
35+
a.d <- a.d +. (b.v *. x.d);
36+
b.d <- b.d +. (a.v *. x.d);
37+
x)
5338

5439
let grad f x =
5540
let x = mk x in

dune

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
(names dyn_wind)
2020
(modules dyn_wind))
2121

22+
(executables
23+
(names dynamic_state)
24+
(modules dynamic_state))
25+
2226
(executables
2327
(names generator)
2428
(modules generator))

dyn_wind.ml

Lines changed: 13 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -6,47 +6,27 @@ open Effect.Deep
66
let dynamic_wind before_thunk thunk after_thunk =
77
before_thunk ();
88
let res =
9-
match_with thunk ()
10-
{
11-
retc = Fun.id;
12-
exnc =
13-
(fun e ->
14-
after_thunk ();
15-
raise e);
16-
effc =
17-
(fun (type a) (e : a Effect.t) ->
18-
Some
19-
(fun (k : (a, _) continuation) ->
20-
after_thunk ();
21-
let res' = perform e in
22-
before_thunk ();
23-
continue k res'));
24-
}
9+
match thunk () with
10+
| v -> v
11+
| exception e -> after_thunk (); raise e
12+
| effect e, k ->
13+
after_thunk ();
14+
let res' = perform e in
15+
before_thunk ();
16+
continue k res'
2517
in
2618
after_thunk ();
2719
res
2820

29-
type _ Effect.t += E : unit Effect.t
21+
type _ eff += E : unit eff
3022

3123
let () =
3224
let bt () = Printf.printf "IN\n" in
3325
let at () = Printf.printf "OUT\n" in
3426
let foo () =
35-
Printf.printf "peform E\n";
36-
perform E;
37-
Printf.printf "peform E\n";
38-
perform E;
27+
Printf.printf "perform E\n"; perform E;
28+
Printf.printf "perform E\n"; perform E;
3929
Printf.printf "done\n"
4030
in
41-
try_with (dynamic_wind bt foo) at
42-
{
43-
effc =
44-
(fun (type a) (e : a Effect.t) ->
45-
match e with
46-
| E ->
47-
Some
48-
(fun (k : (a, _) continuation) ->
49-
Printf.printf "handled E\n";
50-
continue k ())
51-
| _ -> None);
52-
}
31+
try dynamic_wind bt foo at with
32+
| effect E, k -> Printf.printf "handled E\n"; continue k ()

dynamic_state.ml

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
open Effect
2+
open Effect.Deep
3+
4+
(* This file contains a collection of attempts at replicating ML-style
5+
references using algebraic effects and handlers. The difficult thing
6+
to do is the dynamic creation of new reference cells at arbitrary
7+
types, without needing some kind of universal type or dynamic type
8+
checking. *)
9+
10+
module type Type = sig type t end
11+
module Int = struct type t = int let compare = compare end
12+
13+
module LocalState (R : sig type t end) = struct
14+
type reff = R.t
15+
type _ eff += New : int -> R.t eff
16+
type _ eff += Get : R.t -> int eff
17+
type _ eff += Put : R.t * int -> unit eff
18+
end
19+
20+
module type StateOps = sig
21+
type reff
22+
type _ eff += New : int -> reff eff
23+
type _ eff += Get : reff -> int eff
24+
type _ eff += Put : reff * int -> unit eff
25+
end
26+
27+
(**********************************************************************)
28+
(* version 1 : doesn't work, because declaration of new effect names
29+
is generative, so the handler and the client get different versions of
30+
the 'New', 'Get' and 'Put' effects. *)
31+
32+
let run main =
33+
let module S = LocalState (Int) in
34+
let module IM = Map.Make (Int) in
35+
let comp =
36+
match main (module Int : Type) with
37+
| effect (S.New i), k ->
38+
fun s -> let r = fst (IM.max_binding s) + 1
39+
in continue k r (IM.add r i s)
40+
| effect (S.Get r), k ->
41+
fun s -> continue k (IM.find r s) s
42+
| effect (S.Put (r, i)), k ->
43+
fun s -> continue k () (IM.add r i s)
44+
| x -> fun s -> x
45+
in
46+
comp IM.empty
47+
48+
let main (module T : Type) =
49+
let module S = LocalState(T) in
50+
let x = perform (S.New 1) in
51+
perform (S.Put (x, 5));
52+
perform (S.Get x)
53+
54+
(**********************************************************************)
55+
(* version 2 : working creation of freshly generated state cells, but
56+
only an int type. *)
57+
58+
let run2 main =
59+
let module S = LocalState (Int) in
60+
let module IM = Map.Make (Int) in
61+
let comp =
62+
match main (module S : StateOps) with
63+
| effect (S.New i), k ->
64+
fun s ->
65+
let r = if IM.is_empty s then 0 else fst (IM.max_binding s) + 1
66+
in continue k r (IM.add r i s)
67+
| effect (S.Get r), k ->
68+
fun s -> continue k (IM.find r s) s
69+
| effect (S.Put (r, i)), k ->
70+
fun s -> continue k () (IM.add r i s)
71+
| x -> fun s -> x
72+
in
73+
comp IM.empty
74+
75+
let main2 (module S : StateOps) =
76+
let open S in
77+
let x = perform (New 1) in
78+
perform (Put (x, 5));
79+
perform (Get x)
80+
81+
(**********************************************************************)
82+
(* version 3, static creation of new state cells, requiring nested
83+
handlers. Similar to the example in "state.ml". *)
84+
module type GetPutOps = sig
85+
type t
86+
type _ eff += Get : t eff
87+
type _ eff += Put : t -> unit eff
88+
end
89+
90+
module MakeGetPut (T : sig type t end) () = struct
91+
type t = T.t
92+
type _ eff += Get : t eff
93+
type _ eff += Put : t -> unit eff
94+
end
95+
96+
let run3 (type a) (module S : GetPutOps with type t = a) (s : a) main =
97+
let module IM = Map.Make (Int) in
98+
let comp =
99+
match main () with
100+
| effect S.Get, k ->
101+
fun (s : S.t) -> continue k s s
102+
| effect (S.Put i), k ->
103+
fun s -> continue k () i
104+
| x -> fun s -> x
105+
in
106+
comp s
107+
108+
module S1 = MakeGetPut (struct type t = int end) ()
109+
module S2 = MakeGetPut (struct type t = string end) ()
110+
111+
let test3 () =
112+
perform (S1.Put 5);
113+
let x = perform (S1.Get) in
114+
perform (S2.Put (string_of_int x ^ "xx"));
115+
perform S2.Get
116+
117+
(* XXX avsm: disabled pending port to multicont (uses clone_continuation)
118+
119+
(**********************************************************************)
120+
(* version 4. Uses dynamic creation of new effect names to simulate
121+
the creation of new reference cells. Initially, there is only one
122+
effect 'New', which can be used to dynamically create new effect
123+
names. The handler for 'New' wraps the continuation in a new
124+
handler that handles the freshly generated effect names. This setup
125+
yields the same interface as ML refs, except that there is no way
126+
to compare references for equality. This is because cells are
127+
represeted as objects with a pair of a 'write' method and a 'read'
128+
method, so it is possible to create new references that reference
129+
the same underlying data without the access objects being
130+
equal. This is similar to the situation in Idealised Algol, where
131+
variables are ways to affect the state, but have no independent
132+
existence of their own.
133+
134+
Compared to the example in "ref.ml", this implementation does not
135+
require a universal type, nor does it have "impossible" cases.
136+
137+
This example also includes an unneccessary extra 'Choice' effect to
138+
demonstrate the combination of other effects with state in the same
139+
handler. This uses the experimental Obj.clone_continuation function to clone
140+
continuations. *)
141+
type 'a reff = < get : 'a; put : 'a -> unit; internals : (module GetPutOps with type t = 'a) >
142+
143+
effect New : 'a -> 'a rEffect.t
144+
effect Choice : bool
145+
146+
let run4 main =
147+
let donew : type a b. (a reff, b) continuation -> a -> b = fun k ->
148+
let module Ops = MakeGetPut (struct type t = a end) () in
149+
let cell = object
150+
method get = perform Ops.Get
151+
method put x = perform (Ops.Put x)
152+
method internals = (module Ops : GetPutOps with type t = a)
153+
end
154+
in
155+
match continue k cell with
156+
| effect Ops.Get k -> fun s -> continue k s s
157+
| effect (Ops.Put v) k -> fun s -> continue k () v
158+
| x -> fun s -> x
159+
in
160+
match main () with
161+
| effect (New v) k -> donew k v
162+
| effect (Choice) k -> let k' = Obj.clone_continuation k in continue k true; continue k' false
163+
| x -> x
164+
165+
let newref i = perform (New i)
166+
167+
let (:=) r x = r#put x
168+
169+
let (!) r = r#get
170+
171+
let test4 () =
172+
let a = newref 0 in
173+
let b = newref "str" in
174+
if perform Choice then
175+
begin a := String.length !b;
176+
b := string_of_int !a;
177+
print_endline !b
178+
end
179+
else
180+
print_endline !b
181+
*)

0 commit comments

Comments
 (0)