Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The behaviour when we catch an effect which is not a part of Miou #10

Open
dinosaure opened this issue Sep 9, 2023 · 6 comments
Open
Labels

Comments

@dinosaure
Copy link
Contributor

dinosaure commented Sep 9, 2023

Currently, if an application emits an effect which is not defined by Miou, we raise an exception. It will be nice to think about the right behavior to have in such situation.

This has been mentioned at https://discuss.ocaml.org/t/ann-miou-a-simple-scheduler-for-ocaml-5/12963/9

@dinosaure
Copy link
Contributor Author

Since #11, we are able to compose Miou with something else which emits effects. The idea is to keep the rule: an effect suspends a task. Miou.run has a new argument: handler. An example of the composition is done via a test:

type _ Effect.t += Foo : unit Effect.t

let prgm () =
  let prm = Miou.call @@ fun () -> Effect.perform Foo in
  Miou.await_exn prm

let handler_foo fn v =
  let open Effect.Deep in
  let retc = Fun.id in
  let exnc = raise in
  let effc : type c. c Effect.t -> ((c, 'a) continuation -> 'b) option =
    function
    | Foo -> Some (fun k -> continue k ())
    | _ -> None
  in
  match_with fn v { retc; exnc; effc }

let () =
  Miou.run ~handler:{ Miou.handler= handler_foo } @@ fun () ->
  let prm = Miou.call @@ fun () -> Effect.perform Foo in
  Miou.await_exn prm

let () =
  Miou.run ~handler:{ Miou.handler= handler_foo } @@ fun () ->
  let prm = Miou.call_cc @@ fun () -> Effect.perform Foo in
  Miou.await_exn prm

type _ Effect.t += Bar : unit Effect.t

let handler_bar fn v =
  let open Effect.Deep in
  let retc = Fun.id in
  let exnc = raise in
  let effc : type c. c Effect.t -> ((c, 'a) continuation -> 'b) option =
    function
    | Bar -> Some (fun k -> continue k ())
    | _ -> None
  in
  match_with fn v { retc; exnc; effc }

let ( <.> ) { Miou.handler= foo } { Miou.handler= bar } =
  { Miou.handler= (fun fn v -> (foo (bar fn)) v) }

let () =
  let foo = { Miou.handler= handler_foo } in
  let bar = { Miou.handler= handler_bar } in
  Miou.run ~handler:(foo <.> bar) @@ fun () ->
  let prm0 = Miou.call @@ fun () -> Effect.perform Foo in
  let prm1 = Miou.call @@ fun () -> Effect.perform Bar in
  Miou.await_exn prm0; Miou.await_exn prm1

I let this issue open due to the experimental stage of this design. Feel free to debate about the best to provide on the API level.

@polytypic
Copy link
Contributor

polytypic commented Oct 11, 2023

I would recommend not handling unknown effects and allowing the runtime to raise the Unhandled exception.

This allows library and application code to handle the Unhandled exception — to e.g. provide default behaviour, or allows the exception to signal the bug (an unhandled effect).

Note that this comment does not concern the use of some sort of extension mechanism to e.g. add user defined handlers to (the fibers started by) a scheduler. What I just mean is that OCaml already has a notion of an Unhandled effect, it has some use cases, and it should not be interfered with.

@dinosaure
Copy link
Contributor Author

So currently, if we don't handle an effect (the user has neither specified a Miou handler nor attached a handler to the performer), the basic Unhandled exception is raised, as shown in this example:

# type _ Effect.t += Foo : unit Effect.t
# let () = Miou.run @@ fun () -> Effect.perform Foo;;
Exception: Stdlib.Effect.Unhandled(Foo)

What happens is that Miou catches the effect and then re-performs the effect. We do this to respect the rule: an effect supends the task. What do you think?

@polytypic
Copy link
Contributor

We do this to respect the rule: an effect supends the task. What do you think?

Does this mean that an effect that is unkown to Miou basically has the same effect as Yield?

Basically, using Effect.perform with Unhandled one can write

try Effect.perform My_effect
with Effect.Unhandled My_effect ->
  default_implementation_of_my_effect ()

to allow an effect to be optionally handled with a default fallback behavior. This is reasonably performant as setting an exception handler does not allocate and only requires a few instructions.

If a scheduler would then go out of its way to handle the effect by suspending the fiber and then later reperforming the effect, then it would potentially make the above pattern unusably slow. It would also mean that the effect would not be synchronous/atomic (between fibers running on the scheduler), which is another limitation.

So, my recommendation is that any unknown effects should not be handled. Just return None and let the runtime immediately raise the Unhandled exception.

@dinosaure
Copy link
Contributor Author

dinosaure commented Oct 11, 2023

Does this mean that an effect that is unkown to Miou basically has the same effect as Yield?

Yes, that the initial idea. Your pattern is indeed valid and, in the choice to catch up and relaunch, this code doesn't work as intended:

# type _ Effect.t += Foo : unit Effect.t
# let () = Miou.run @@ fun () ->
  try Effect.perform Foo
  with Effect.Unhandled Foo -> () ;;
Exception: Stdlib.Effect.Unhandled(Foo)

However, if you want to manage this kind of pattern, you can't just ensure that an effect is like Yield. I don't have any strong opinions on this and we could just declare that the production of effects specified by Miou are the only ones that suspend a task. I need to think more about that and find the right cursor 👍 - but if you have some concerns, please share!

@ada2k
Copy link
Contributor

ada2k commented Jul 13, 2024

I would agree with polytopic here. If Effects are to become a more widely used language feature outside of schedulers, I believe Miou should only handle it's own - doing otherwise is just going to put Miou at odds with any novel uses for effects that happen to need to catch Unhandled.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

No branches or pull requests

3 participants