行動-行動Erlang

こんにちは



最初にあなた自身について少し話してください。 私は約10年間Erlangについて書いていますが、最近のスキームと図を歓迎します。 しかし、コードのフリップがビヘイビアーの使用を引き起こしたことを覚えています。これは複雑な製品にとって興味深いトピックだと思います。



なぜ行動が必要なのですか? 振る舞いは、インターフェース定義の本質です。 呼び出し元と実装の間の契約を設定します。 さて、クラスインターフェイスの通常の定義の場合、これはすべてこれに由来します。 この場合にのみ、モジュールのインターフェースを定義します。



モジュールは複数の動作を実行できますが、動作が重ならないように注意する必要があります。



動作がパラメータの名前と数に一致する関数を宣言する場合、コンパイル中に競合する動作の論理的な警告が表示されます。



構文面は非常に単純です。 例のコードを入れて、説明を続けます。

行動規範:



-module(sample_behavoiur). -export([default/2]). -callback init(Args :: list())-> {ok, State :: term()}. -callback action(State :: term())-> {ok, ActionResult :: term(), State::term()} | {error, ErrorInfo :: term() }. -callback default(State :: term() )-> {ok, DefaultResult :: term() }. -optional_callbacks([default/1]). -spec default(Mod :: atom(), State :: term())-> {ok, DefaultResult :: term() }. default(Mod, State)-> case erlang:function_exported(Mod, default, 1) of true -> Mod:default(State); false -> {ok, default} end.
      
      





これらの動作を実行する2つのモジュール:

 -module(implement_1). -behaviour(sample_behavoiur). -export([init/1, action/1 ]). -record(state,{list :: [integer()]}). init(Args) -> {ok, #state{list = Args}}. action(State = #state{list = []})-> {ok, empty, State}; action(State = #state{list = [Head|Rest]})-> {ok, Head, State#state{list = Rest}}. -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). simple_test()-> {ok, Opaq1} = implement_1:init([1,2,3]), {ok, 1, Opaq2} = implement_1:action(Opaq1), {ok, 2, Opaq3} = implement_1:action(Opaq2), {ok, 3, Opaq4} = implement_1:action(Opaq3), {ok, empty, Opaq5} = implement_1:action(Opaq4), {ok, default} = sample_behavoiur:default(implement_1, Opaq4). -endif.
      
      







 -module(implement_2). -behaviour(sample_behavoiur). -export([init/1, action/1, default/1 ]). -record(state,{list :: [integer()]}). init(Args) -> {ok, #state{list = Args}}. action(State = #state{list = []})-> {ok, empty, State}; action(State = #state{list = [Head|Rest]})-> {ok, Head, State#state{list = Rest}}. default(_State = #state{list = []})-> {ok, empty}; default(_State = #state{list = [Head|_]})-> {ok, Head}. -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). simple_test()-> {ok, Opaq1} = implement_2:init([1,2,3]), {ok, 1, Opaq2} = implement_2:action(Opaq1), {ok, 2} = sample_behavoiur:default(implement_2, Opaq2), {ok, 2, Opaq3} = implement_2:action(Opaq2), {ok, 3, Opaq4} = implement_2:action(Opaq3), {ok, empty, Opaq5} = implement_2:action(Opaq4), {ok, empty} = sample_behavoiur:default(implement_2, Opaq4) -endif.
      
      







ご覧のとおり、この動作では3つの関数が宣言されていますが、そのうちの1つはオプションです。 通常、オプションの関数は、動作自体の本体の安全なハンドラーにラップされ、実行中のモジュールに関数が存在するかどうかがチェックされます。 関数の宣言には仕様が含まれており、可能な限り詳細に仕様を作成することをお勧めします。 これにより、ダイアライザーを使用した静的コード検証が容易になります。



動作を実装するモジュールでは、デフォルト関数を除いて、例ではほとんど同じですが、関数宣言が動作定義にあるものを繰り返すことは明らかです。 実際には、特定の実装がすべての可能なシナリオを作成しない場合、宣言は元の関数の任意のサブセットにすることができます。



より興味深いケースは、ビヘイビアモジュール自体が別のビヘイビア自体を実行するときの一連のビヘイビアの作成です。 スーパーバイザーの動作がgen_serverの動作を順番に実行する場合、Erlang標準ライブラリの上記のプライバシーに例を見ることができます。 この場合、コードは2つの部分に分割されます。最初の部分は契約上の義務を果たし、2番目の部分は新しい契約の下で他のモジュールに公式の機能を提供します。



機能コードは、常に新しい動作を定義する必要があるとは限りません。 そして、必要性の基準は単純です-同じセマンティクスまたは役割を持つ2つ以上のモジュールは、インターフェースの定義と統合を必要とします。 費やした時間は、テストと将来のコード拡張の両方を促進します。 1つの役割を持つモジュールが2〜3個ある場合、さらにいくつかのモジュールが存在する可能性が高いためです。



All Articles