Promoting a function to a deep continuation

The example for deep handlers in the docs has this following paragraph:

The computation f may also perform effects. If f performs the Yield effect, the current task is suspended (inserted into the queue of ready tasks), and the next task from the scheduler queue is run. If the effect is Fork f, then the current task is suspended, and the new task f is executed immediately via a tail call to spawn f. Note that this choice to run the new task first is arbitrary. We could very well have chosen instead to insert the task for f into the ready queue and resumed k immediately.

Maybe I am missing something obvious, but given this scheduler example:

type _ Effect.t += Fork : (unit -> unit) -> unit t
                 | Yield : unit t

(* A concurrent round-robin scheduler *)
let run (main : unit -> unit) : unit =
  let exchanger : (int * (int, unit) continuation) option ref =
    ref None (* waiting exchanger *)
  in
  let run_q = Queue.create () in (* scheduler queue *)
  let enqueue k v =
    let task () = continue k v in
    Queue.push task run_q
  in
  let dequeue () =
    if Queue.is_empty run_q then () (* done *)
    else begin
      let task = Queue.pop run_q in
      task ()
    end
  in
  let rec spawn (f : unit -> unit) : unit =
    match f () with
    | () -> dequeue ()
    | exception e ->
        print_endline (Printexc.to_string e);
        dequeue ()
    | effect Yield, k -> enqueue k (); dequeue ()
    (* How to enqueue f into the queue here? *)
    | effect (Fork f), k -> enqueue k (); spawn f
    | effect (Xchg n), k ->
        begin match !exchanger with
        | Some (n', k') -> exchanger := None; enqueue k' n; continue k n'
        | None -> exchanger := Some (n, k); dequeue ()
        end
  in
  spawn main

How would one enqueue f into run_q?

I think you could avoid the enqueue function and work with run_q directly.

Instead of

    | effect (Fork f), k -> enqueue k (); spawn f

you reverse the order and do this:

    | effect (Fork f), k -> Queue.push (fun () -> spawn f) run_q; continue k ()

Ah got it thank you! I thought (‘a,’b) continuation was being enqueued into run_q but it’s actually let task () = continue k v. Then f can directly be enqueued.

But it seems this version requires spawn to wrap it, since the handler is not wrapped around f when it is called inside the handler. Any thoughts if this is unavoidable, otherwise it seems that it is not quite symmetric with the scheduler given in the example? I was just wondering that like fiber in Effect.Shallow, is there a reason why Effect.Deep does not have such as a primitive?