Arantium Maestum

プログラミング、囲碁、読書の話題

OCaml Effects Tutorialの演習「Generators from iterators」をやってみる3

前回の終わりに書いた通り、Effect.Shallowを使って「generatorが作成されてまだ要素を返していない」状態を特殊ケース化せずに処理できるようにする。

今回のコード

type ('elt, 'container) iterator = ('elt -> unit) -> 'container -> unit

type 'elt generator = unit -> 'elt option

let generate (type elt) (i : (elt, 'container) iterator) (c : 'container) : elt generator =
  let module S = Effect.Shallow in
  let open struct
    type _ Effect.t += Yield : elt -> unit Effect.t
    type status =
    | Paused of (unit, unit) S.continuation
    | Done
  end in
  let yield x = Effect.perform (Yield x) in
  let status = ref @@ Paused (S.fiber (fun () -> i yield c)) in
  let handler = {
    S.retc = (fun () -> status := Done; None);
    S.exnc = (fun e -> raise e);
    S.effc = (fun (type b) (eff : b Effect.t) ->
      match eff with
      | Yield x ->
        let handle_eff (k: (b,unit) S.continuation) =
          status := Paused k;
          Some x
        in
        Some handle_eff
      | _ -> None)
  } in
  fun () -> match !status with
  | Paused k -> S.continue_with k () handler
  | Done -> None

ポイントは四つ。

Effect.DeepではなくShallowを使っている:

  let module S = Effect.Shallow in

status型からStartingバリアントが再度消えている:

    type status =
    | Paused of (unit, unit) S.continuation
    | Done

ついでに限定継続の型も(unit, elt option) D.continuationから(unit, unit) S.continuationに変わっている。Effect.Shallowを使う影響で、限定継続がハンドラを含まなくなっているので、最終的に限定継続から返ってくる値の型はunitになる。Deepの場合は限定継続の中でハンドラが()を受け取りNoneを返していたのでelt option型となっていた。

Startingバリアントが消えたことに伴い最後のパターンマッチ関数でもStartingのケースが消えている:

  fun () -> match !status with
  | Paused k -> S.continue_with k () handler
  | Done -> None

(ついでにDeepではなくShallowを使っている影響でD.continue k ()S.continue_with k () handlerに変わっている)

最後にstatus値の初期化がStartedではなくPausedで始まること:

  let status = ref @@ Paused (S.fiber (fun () -> i yield c)) in

この最後のポイントが肝心。

Effect.Deepではあるハンドラの影響下でエフェクトの生じる処理を開始するmatch_withと、その処理のなかで生じたエフェクトから返ってきた限定継続を再度実行するcontinueがわかれている。そのためgeneratorから最初の要素を取り出す時はmatch_with、それ以降はcontinueを呼ぶ必要がある。

Effect.Shallowは単にcontinue_withのみがあり、初めてcontinue_withを呼ぶときのために「ただの関数を限定継続として扱えるようにする」fiber関数を提供しているので、初期値に独特のバリアントを用意する必要もなくただgeneratorが停止しているかどうかだけを気にしていればよく、ケースわけも減ってスッキリした記述となる。

次回

Effect.DeepでもStartingバリアントなしにできなくはないのだが、そのための記述量がEffect.Shallowに比べて非常に多いのであまりスッキリはしない。次はそのDeepを使った書き方の話(次といいつつすぐ書くかはわからないが、とりあえずこの話題に関しての続きという意味で・・・)。