5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

OCamlで説明するgen_serverの使い方

Last updated at Posted at 2017-05-11

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)となる。

gen_server.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で決める。

gen_server.mli
(**
 * 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での例

kitty_server.ml
module KittySeed : Gen_server.Seed = struct
  type state = cat list
  type args = unit
  let init () = Ok []
  ...
end

call系の同期的なAPI

gen_server.mli
(**
 * 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での例

kitty_server.ml
module KittySeed : Gen_server.Seed = 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関数について

省略

5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?