Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save yoshihiro503/28df2e5d93f77d3ecb774f3052042200 to your computer and use it in GitHub Desktop.
Save yoshihiro503/28df2e5d93f77d3ecb774f3052042200 to your computer and use it in GitHub Desktop.
OCamlで説明するgen_serverの使い方

OCamlで説明するgen_serverの使い方

Erlang/OTPを勉強し始めたので、自分が理解するためにOCamlの型でgen_serverを理解したい。間違いの指摘など大歓迎。 gen_serverを完璧に型付けすることが目的ではない。なんとなく理解できれば良いつもり。

想定する読者

  • OCaml使い
  • Erlang/OTPの勉強中の人
  • (optional) すごいE本の14章を読んだ

バージョンについて

  • Erlang/OTP 19
  • OCaml 4.04.1

ビヘイビア

behaviourはOCamlでいうところのファンクタだと思う。 想定するcallback群をSeedという名前のモジュールタイプで表現し、Seedを前提として提供される関数群をMakeという名前のファンクタで表す。

つまり、gen_serverビヘイビアみたいなのをOCamlのgen_server.mlで提供するならば次のようなインターフェイス(mli)となる。

(**
 * OCamlの型でErlang/OTP gen_server のインターフェイスを説明したい
 * gen_serverを使う人が用意すべき関数群(Erlangではcallbackと呼ぶ)
 *)
module type Seed = sig
  ...
end

(* 上記のSeedが与えられると、gen_serverは以下の関数群を提供する *)
module Make(S : Seed) : sig
  ...
end

gen_serverをOCamlで実装したいわけではないので、以下ではgen_server.mliを考えることにする。

init

gen_serverによって生成されるプロセスが内部で保持する状態の型stateと、プロセス起動時の初期状態を決める方法initをSeedで決める。

(**
 * OCamlの型でErlang/OTP gen_server のインターフェイスを説明したい
 * とりあえず、initのみ
 * gen_serverを使う人が用意すべき関数群(Erlangではcallbackと呼ぶ)
 *)
module type Seed = sig
  type state (* プロセスが内部で保持する状態の型、外からは見えない *)

  type args  (* プロセス起動時に必要とするパラメータの型 *)
  val init : args -> (state, exn) result (* プロセス開始時の内部状態を構成する *)
  ...
end

(* 上記のSeedが与えられると、gen_serverは以下の関数群を提供する *)
module Make(S : Seed) : sig
  val start : S.args -> start_option list -> (pid, exn) result
  val start_link : S.args -> start_option list -> (pid, exn) result
  ...
end

すごいE本 kitty_serverでの例

module KittySeed = struct
  type state = cat list
  type args = unit
  let init () = Ok []
  ...
end

call系の同期的なAPI

(**
 * OCamlの型でErlang/OTP gen_server のインターフェイスを説明したい
 * とりあえず、同期的なやりとりのためのcall系のみ
 * gen_serverを使う人が用意すべき関数群(Erlangではcallbackと呼ぶ)
 *)
module type Seed = sig
  type state (* プロセスが内部で保持する状態の型、外からは見えない *)

  ...
  type call_request  (* プロセスが提供することになる同期APIの入力 *)
  type call_response (* プロセスが提供することになる同期APIの出力 *)

  type handle_call_result = (*hibernateとtimeoutについては省略*)
    | Reply of call_response * state
    | NoReply of state
    | Stop of reason * call_response option * state

  val handle_call : call_request -> (pid*tag) -> state -> handle_call_result
end

(* 上記のSeedが与えられると、gen_serverは以下の関数群を提供する *)
module Make(S : Seed) : sig
  ...
  val call : pid -> S.call_request -> timeout option -> S.call_response
  ...
end
  • handle_callでNoReplyを返すときはreply関数を直接どこかで呼んでいないといけない (;゚ ロ゚ )
  • なるべくReplyを返すようにするのがお行儀が良いのかもしれない
  • handle_call_resultにおける hibernateやtimeoutの更新に関してはあまり本質じゃない気がしたので省略した

すごいE本 kitty_serverでの例

module KittySeed = struct
  type state = cat list
  ...
  type call_request = (* kitty_serverが提供する同期的なAPIは次の二種類 *)
    | OrderCat of cat
    | Terminate
  type call_response =
    | OrderOk of cat
    | TerminateOk of cat list
  ...
end

本当は call_response 型はcall_requestに依存して変わるのでGADTなど使った方がより強い型付けができるかもしれないが、ここではなんとなく理解する目的なので深追いはしない。

cast系の非同期的なAPI

だいたいcall系とおんなじノリ。requestやresultはcast系と別の型にできるっていうのがポイントなのかも。

その他のcallback関数について

省略

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