Created
April 19, 2016 14:44
-
-
Save dhil/cf510487238863fc1571be7589155dff to your computer and use it in GitHub Desktop.
Encoding effects á la Links in OCaml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Operations are structural typed in Links, therefore operation names are not declared before use. | |
In OCaml operations are nomial. This difference is not really an issue as we can simply collect all operations from the Links IR and declare them globally in OCaml. | |
However, Links' presence polymorphism introduces a small problem as operation signatures can change throughout a computation! | |
As an example of this consider the following: | |
sig fooInt : (() {Foo:(Int) {}-> Int|e}~> Int) -> | |
() {Foo{_} |e}~> Int | |
fun fooInt(m)() { | |
open handle(m) { | |
case Foo(42,k) -> k(1) | |
case Foo(n,k) -> k(0) | |
case Return(x) -> x | |
} | |
} | |
Here, Foo has type (Int) {}-> Int | |
and below Foo has type (Bool) {}-> Int: | |
sig fooBool : (() {Foo:(Bool) {}-> Int|e}~> Int) -> | |
() {Foo{_} |e}~> Int | |
fun fooBool(m)() { | |
open handle(m) { | |
case Foo(true,k) -> k(42) | |
case Foo(false,k) -> k(-42) | |
case Return(x) -> x | |
} | |
} | |
Now, we may introduce a middle man that invokes Foo : (Bool) {}-> Int: | |
sig middleMan : (() {Foo:(Bool) {}-> Int|e}~> Int) -> | |
() {Foo:(Bool) {}-> Int|e}~> Int | |
fun middleMan(m)() { | |
open handle(m) { | |
case Return(x) -> do Foo(if (x == 1) true else false) | |
} | |
} | |
Putting these together in Links, we get: | |
links> fooBool( middleMan( fooInt( fun () { do Foo(42) } ) ) ); | |
fun : () {Foo{_}|_}~> Int | |
Note in the inner computation, we invoke Foo : (Int) {}-> Int. | |
Running the above yields 42: | |
links> fooBool( middleMan( fooInt( fun () { do Foo(42) } ) ) )(); | |
42 : Int | |
How do we encode this in OCaml effects/multicore? | |
A first attempt would be to declare Foo as | |
effect Foo : 't -> int | |
But this won't get us very far, because 't remains an abstract type: | |
# let fooInt m = match m () with | |
| v -> v | |
| effect (Foo 42) k -> continue k 1 | |
| effect (Foo _) k -> continue k 0;; | |
Error: This pattern matches values of type int | |
but a pattern was expected which matches values of type t#1 | |
A possible solution is to wrap Foo inside a module: | |
module Foo (F : sig type t end) = struct | |
type t = F.t | |
effect Foo : t -> int | |
end | |
and then instantiate Foo with int and bool, e.g. | |
module Foo_int = Foo (struct type t = int end) | |
module Foo_bool = Foo (struct type t = bool end) | |
Now, we can define foo_int, foo_bool and middle_man handlers: | |
let foo_int m = | |
match m () with | |
| x -> x | |
| effect (Foo_int.Foo 42) k -> continue k 1 | |
| effect (Foo_int.Foo _) k -> continue k 0 | |
let foo_bool m = | |
match m () with | |
| x -> x | |
| effect (Foo_bool.Foo true) k -> continue k 42 | |
| effect (Foo_bool.Foo false) k -> continue k (-42) | |
let middle_man m = | |
match m () with | |
| x -> perform (Foo_bool.Foo (if x > 0 then true else false)) | |
Now, we can put the above together, and we obtain 42 as desired: | |
# foo_bool( fun () -> middle_man( fun () -> foo_int (fun () -> perform (Foo_int.Foo 42) ) ) );; | |
- : int = 42 | |
However, this encoding seems rather heavy and I imagine that it'd be awkward to generate. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment