Elixirというか、BEAM VMのプロセスが並行に動いてることをさくっと確認するため、個々のプロセスに適当な時間sleepした後にメッセージを返すだけの簡単なプログラムを組んでみました。 テストコード付き。 以下をコピペして、 elixir copy_file.exs で実行するとテストが通ります。 これを個別に実行すると以下のような感じ。 以下は、{:ok, プロセスID, 各プロセスにメッセージを送った順, sleepした時間} です。 これを見ると、個々のプロセスがそれぞれ並行して動作していて、一定時間待ったらgroup_leaderへメッセージを返している、という処理がプロセス毎に並行して実施されていることが確認できます。 雑に並行処理を書いてみましたが、これ、少し遊ぶと個々のプロセスを参加者とみなした問題の早解きとかできそうですね。はたまた、個々のプロセスが独立してるとすれば、社会システムを模倣したちょっとしたシミュレーションとかできそう。(やる意味は置いておいて…) 雑に処理を並行させてみた、でした。More
Category Archives: development
[Elixir]EctoのQueryを少し読み解く ~ コードに前提条件が書かれるので読み解きやすいですね ~
Ectoは、Elixirが標準として提供するDBへの接続ライブラリです。macroを学んだので、少しmacroを読んでみよう、ということで読んでみました。 ここでは、すべてEcto v0.16.0を元にしています。 Ecto.Queryの補完を見ればわかるのですが、この下にfromなどのSQL構文を持っています。 これらを使い、例えば、 Sample.User というリポジトリに含まれる要素に対してSQLを発行したい場合に以下のようにSQLを発行することができます。 これらを読んでいこうとすると、まずは以下の fromに関するdefmacro に出会います。 defmacro from(expr, kw \\ []) do コード このコードを読んでいくと、このmacroの先で幾つかの条件に分けれられた from が定義されています。 defp from([{type, expr}|t], env, count_bind, quoted, binds) when type in @binds do コード defp from([{type, expr}|t], env, count_bind, quoted, binds) when type in @no_binds do コード defp from([{join, expr}|t], env, count_bind, quoted, binds) when join…More
[Elixir]macroのテストを書いてみる
macroのテストを書く macroのテストでは、通常は コンパイルされたモジュールの振る舞い コンパイルされた結果(AST)自体 の2つの側面をテスト対象と見てテストコードを書いていくことになります。 振る舞いに関しては、以下のような感じでExUnitを使います。こちらは、普通にmacroによって定義された関数を使って、期待するinput/outputを確認する、というものです。 コンパイル自体に対しては、以下のようにAST自体が正しくできているか?を確認することになります。他は↑とさほど変わりません。inputがASTの文字列、outputがassertionになります。 例えば、以下のような内容になります。 少し話が逸れますが、Agentなどのテストにおいて、Processにsendされた値を確認したい時があります。つまり、mailboxの中身を確認したい。そんな時は以下のように __mailbox__ を使うことができます。 いつMacroを使うか? より簡潔にコードを記述できる場合 コード生成が必要な場合 例えば、Phoenixのルーティングではコード生成によってGET/POST/PUT/PATCH/DELETEなどの match を定義しています。 このような、少量のコードを書くことで、大量の汎用的なコードを生成する、というような場合、それら全てを手で書くよりも正しく、効率的に実行コードを生成できます ただし、複雑になると理解が難しくなるので、metaprogramingで大事なことは、simpleであることです。 mix-inするとき、 import を使えるなら use は使わない use を使うと、 __using__ を読み込みます。そのため、mix-inとしてimportのように使うことができます。ただ、macroをmix-inのためだけに使うのは止めましょう。単に複雑さを増すので。 macroを使ったmix-in: NO importを使ったmix-in: OK 他、いろいろありましたが大事なところはここら辺かな。 ここまでの内容は、『Metaprogramming Elixir』を読んだ内容が主でした。 __using__ の使い所とか、テストフレームワークとか作るのに必要なメタプログラミングの知見を得られたかな、という感じで、良い学びでした。ついでに、ちょくちょくElixir関連のOSSに貢献できたので、それも良かったかな 🙂 関連 [Elixir]macroを書くときの影響範囲 [Elixir]Macroで簡単なテストフレームワークを作ってみるMore
[Elixir]Macroで簡単なテストフレームワークを作ってみる
テストフレームワークと書きながら、そこは余り多く書いていませんが… Elixirは、 Elixir is small because it doesn’t have ti include all common features. とのこと。なので、他に必要な拡張が欲しければ、自分で作ってねという言語らしい。基本は小さく、という立ち方ですね。 bind_quoted 通常、 quote で囲まれたところのうち、変数をそのまま使いたい場合は unquote します。ただ、都度 unquote するのは少し面倒だし、場合によっては意図しない動作になります。その問題を解決するために、 bind_quote があります。 例えば、以下のASTが bind_quote の有無でどう変わるのかを覗いてみます。 unquoteのみ bindされたとき bind_quoteのときは、最初に処理が関連付けられて、それを以降のquoteでaliasのようにして呼び出している、という処理をするようですね。unquoteの有無で意味が大きく異なる処理もあるので、これは良さそうです。 このbind_quoteが有効な時は、unquoteはその範囲内では無効に設定されます。 なお、このほかにも https://github.com/elixir-lang/elixir/blob/f7183440716d705bfaabdc54c216d87cebacd9a7/lib/elixir/src/elixir_exp.erl#L146 にある、 がquoteのときにしていできる、特別なoptionのようです。 テストフレームワーク 簡単なテストフレームワークを書きました。以下リポジトリに突っ込んでいます。 内容自体は簡単で、 assert の実装 test で始まるメソッドをテストケースとして認識、実施する というものです。 https://github.com/KazuCocoa/my_mini_ex_test_assertion/tree/master サンプルを見ながらの実装ですが、これはそんなに難しくないです。 Compile-Time code generation コンパイル時にコードを生成、メソッドなんかで定義されるmacroの話です。 unquoteが、quoteの外で使われている場合があります。 これにより、以下のように動的な関数を定義することができる。 ここで、Elixirは、外部ファイルに依存している箇所を明示することができる。 これにより、Elixirは mimes_path…More
[Elixir]macroを書くときの影響範囲
ElixirのASTって、Lipsの文法をTupleで表現しているようなものなのですね。defmacro、quoteをMacro.expand_onceなんかで分解してみると、 {atom, [List], [List]}の形式で分解されていき、Lipsのような読み方で読み解くことができる、と。 なので、Elixirは Elixirの文法そのままでHigh Levelな表現で処理を記述する LipsのようなLow Levelな表現で処理系を記述する という2層で処理を記述することができる、と。High/Lowは、処理の抽象度を指しています。 後者がMacroになるわけですが、そんなMacroも下手に適用範囲を広げないためにcontextによる適用範囲を限定する機能を持っています。 Elixir Hygiene 以下の通り、 __MODULE__ により、contextが異なることがわかります。 quote で囲まれるところは、つまるところ、呼び出された側のcontextに限定された環境下でのみ、有効になる、ということを意味する。 Elixirはこのようにcontextを設定することで、macroの影響範囲を限定する手段をとっています。 Violate hygiene!! 通常、 quote で囲まれた範囲は、quoteで囲まれた中だけで有効です。 それ以外の領域には影響を及ぼすことはありません。 例えば、以下の通り name の変数は Setter.bind_name のマクロの範囲を出ることはありません。 しかし、以下のように var!/1 を使うとこのhygieneな領域を超えて変数をOverrideすることが可能になります。この機能は、本当に必要なときのみ使いましょう、と強く書かれてもいます。 他メモ Logger.debugは、productionのコンパイルが行われるときに削除される。 なるほど… Macroは処理系を書き換えてしまって、例えば Kernel.if でさえ異なる処理に書き換えることもできるので少し慎重になっていましたが、影響範囲を限定的にできる、その領域を超えるときは明示が必要、という姿勢が良いですね。 ElixirのMacroのドキュメントに以下が書かれていることからも、やっぱり明示に寄せている方に寄せておきたいですね。 Remember that explicit is better than implicit. Clear code is better than concise code. 追記…More
Ecto0.16.0でvalidate_uniqueがdeprecateになって、unique_constraintが追加された
これは、 Changeset の構造体に constraint の要素を設定し、DBへの処理を行うタイミングでその束縛に反していないかを確認する、というもののようです。 変更のあったコミット https://github.com/elixir-lang/ecto/commit/40c7edb69de8a30145e4e4d54cc8108cb1999630 これをざーっと眺めると、 lib/ecto/changeset.ex の moduledoc が更新されていることがわかります。 constraints と validation の関係が記載されているので、気になるかたは眺めてみてください。 However, constraints can only be checked in a safe way when performing the operation in the database. As consequence, validations are always checked before constraints. Constraints won’t even be checked in case validations failed. とのことです。 changeset.ex の defstruct に以下が追加されています。…More
[Elixir in Action]Elixirで分散システムを構成するための諸要素を知る
ここら辺がこの書籍の真骨頂でしょうか。 Chapter11ではよく知られたOTP applicationを作るためのファイル構成とか、書き方な話なのですっ飛ばして、Chapter12のメモ。 ざっと読んで、触った感覚としては、分散システムは無知では手を出さないほうが良いかなーということ。色々考慮漏れで溢れそうな予感… Erlangベースのシステムでは、processとmessageによって分散システムを構成します。(伝統的なRPCと混同しないように) BEAMによる分散システムは、複数のnodeがそれぞれ接続されてクラスタ化されることで実現されます。このnodeは、BEAMインスタンスと呼ばれます。 Nodeの接続 以下のようにして、簡単なnodeを立てて接続、クラスタを組むことができます。 node1 node2 node3 node1とnode2の後に以下を実施。 Clusterをすでに構築しているnodeに接続すると、自動的にそのCluster内の他nodeとの接続も行われます。これは、tick messageと呼ばれるメッセージのやり取りが行われるためです。これにより、Clusterに含まれるnodeの生存確認も行います。すでにdisconnectな状態のnodeがあれば、それは Node.list から除かれます。 Standard I/Oの実行と出力先 以下の通り、node1で実行した内容を、 Node.spawn でnode2に渡すと、その処理はnode2で行われ、結果をnode1で表示する、ということができます。 これは、すべてのstandard I/Oの出力はClusterのgroup leaderに渡されるためです。group leaderは、処理をinputされたnodeで、ここではnode1を指します。 ここで送るmessageには特に制限はないとのこと。このmessageは、 :erlang.termi_to_binary/1 でエンコードして送られて、受け取ったnodeは :erlang.binary_to_term/1 でデコードするそうです。 Cluster構成の前に nodeが互いにやり取りを行うにあたり、必ず以下の操作が必要になります。(送信元をclient、送信先をserverと表現) ClientがServerのPIDを取得する ClientがServerにmessageを送る そのため、まずはPIDを取得する必要があります。 PIDのlookupは基本的にlocalで行われます。そのため、目的のprocessを自身のlocalで見つけること自体は高速に行われます。 PIDにはルールがあって、以下のようになっています。 どのnode上にあるprocessか(localなら、ゼロ) local内でユニークなnodeの番号 ↑のnodeの番号が表現可能な範囲を超えると増加する Global関数を使って広範囲でClusterを構成する Global関数を使って、globalな領域でclusterを組むことができます。このglobalは、Erlangですでに用意されている関数です。 Elixirだと、GenServerなんかで start_link するときとか、 :global 指定で起動することができます。 https://github.com/elixir-lang/elixir/blob/e74852ffcc95872915be2b1aae453e74d6c54325/lib/elixir/lib/gen_server.ex#L132 実際にglobalを使うと、以下のような形でregisterとwhereisでPIDを得ることができます。 以下のように、なんらかの他nodeとリンクを張っていると以下のような情報が取得できます。 pg2関数を使ってグループに分ける :pg2関数を使ってグループを作ることもできます。このpg2関数では、同じエイリアス( :doro_list というところ)に複数のnodeをぶら下げることにより、1つのエイリアスに対して複数のnodeをグループ化できます。 ここでは、node1で元となるprocessを作り、そこにnode2とnode1が順に参加、グループとなる例を示しています。…More
[Elixir in Action]Erlang Term Storageを知る
Getting Startedなどやっているとよく出てくる、定番な機能ですね。ETSは Erlang Term Storage の略です。 ある1つのprocessをキャッシュ代わりに使っている場合、そのprocessの性能や拡張性が処理のボトルネックになることが多くなります。そんなときの解決策としてETSが説明されています。(ETSに限らず、何かの要素と依存関係を持つ場合、その要素のいずれかがボトルネックになることは多々ありますね。) 特徴 Positive etsは、owner processが起動している間だけ使える(in-memoryで) serialize(連続的に)なキャッシュに書き込める 分けられたメモリ空間に書き込まれ、多くのprocessから並行にアクセス可能 globalでアクセス可能 mutable 非常に高速なアクセスが可能 末尾に内容を記載 Negative client processとETS tableはコピーされる ETSに保存されるデータが原因でprocessがcrashする場合、 Supervisorによってリカバリされたprocessも再びcrashする という、悪循環に陥ることがある BEAMの良さであるリカバリシステムを崩す原因になる ETSには、複雑で大きなデータは扱うべきではない ETSはネットワーク越しの他BEAMインスタンスとは共有できない 本当に性能/拡張性を改善したいときだけにするといった用途を制限するほうが良い 高い性能で多くのprcessで共有したいデータか?など ETSの使い方 New http://www.erlang.org/doc/man/ets.html#new-2 table types :set The table is a set table – one key, one object, no order among objects. This is the default table…More
[Elixir in Action]Supervision Tree ~ ネストされたSupervisor ~
メモメモ。 前回では、Supervisorというprocessの説明でした。 ここからは、エラーに関する話やSupervision Treeの話。 Supervision treesは、Supervisorによって以下見たく構成されるSupervisor – processの関係性を指します。Supervisorやworkerがlinkの関係を持ち、strategyに沿ってそれぞれのprocessを監視していきます。 Supervisorには、その子processの起動として、以下を定義しています。 Supervisorは、親から子とprocessを順に起動していきます。このprocessの起動は同期的に行われます。なので、上記では worker(Todo.ProcessRegistry, []) => supervisor(Todo.Database, [“./persist/”]) => … という順に前のprocess起動が終わるのを待って次のprocessが起動していきます。 これらのworkerやsupervisorは、 Sample.Supervisor のprocessが終了するとterminateされていきます。(これがsupervisorと、その子の関係ですね) Erlang/Elixirが提供する Supervisor では、 link や monitor をPIDによって指定したプロセスを対象として監視していたものを、抽象化して、processにひも付けたatomによって監視します。これは、PIDはprocessがcrashした後に起動するたびに変わるためですね。 前回にもメモしていましたが、processの監視には link と monitor があります。子processがcrashしたらそれ単体を再起動する、などしたいならlink、子processがcrashしたらそれに関係する他の子processも終了する、とか実装したいならmonitorを使う必要があります。 Supervisorには、その子processのcrash時の挙動として幾つかのstrategyを定義し、提供しています。それらから必要なstrategyを設定して、実際には運用していくことになります。この選択はfault toleranceなシステムを構築するうえで重要な要素。 :one_for_one – if a child process terminates, only that process is restarted. :one_for_all – if a child process terminates, all…More
[Elixir in Action]fault toleranceを保つための特別な責務を負うprocess~Supervisor~
BEAMの真骨頂であるFault toleranceに関して、複数章に渡って説明しているところの話。 ここではその触りの箇所。 fault torelanceであるようにするためには、failureを知り、それに対してリカバリして現状の機能を提供し続けることが最低限必要です。そのためにBEAMがどのような機構を備えて、Elixir/Erlangではどうなのか?をまとめています。 3つのタイプのエラー BEAMでは3つの種類のerrorを持ちます。 errors ** [ArithmeticError] bad argument in arithmetic expression などで意図せずエラーがでるとき exits exit(“I’m done”) のように、意図的にプロセスを終了させる throw throw(:throw_value) のように、意図的にthrowを投げる throwの目的は、non-localな相手へ何か情報を与える時に使うことです。(BEAMでは、remote processに何か処理を任せる、ということが多々ありますね) Elixirでは、すべてのエラーは 必ず 何らかの値を返します。 そのため、任意のエラーに対して共通で何かする、というときは以下のように _ を使う必要があります。 それ以外では、 catch のパターンマッチを使い、特定のエラーの時は意図した処理を行うような処理を書きます。 defexception により、任意のmacroを組むことが可能。 もし、エラー処理を用意していない場合、 processはterminate されます。制御できないエラーはどんどん落としてしまえ、というやつですね。 errors in concurrent system BEAMではprocessで分離しているので、何らかのprocessのエラーが他processへ影響を与えることはありません。 そのため、processを監視して、互いを把握していく必要があります。 Link Process.link/1 や spawn_link/1 を使うことで、2つの異なるprocessをリンクします。リンクするとは、一方のプロセスが死んだら、もう一方はそれを知ることができるし、その逆も然り、という相互の関係を持つことを指します。 正常にexitするときは :normal という値を返します。それ以外は、基本的に異常終了である、と判断します。 通常、終了のシグナルは以下のメッセージを構成します。 何らかのエラーを投げて死ぬ場合は、以下の形式のメッセージを構成します。…More