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
Tag Archives: program
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
[Elixir in Action]OTP/GenServerを学んで非同期/並行処理を学ぶ
Elixir in Actionの続き。ようやっと3分の2くらい。写経もほどほどに実施しながらだとこのくらいですかね。 Chatpter6、7ではOpen Telecom Platformと、その上に構成されるGenServerに関する内容をざーっと通してました。 自前のloopで作っていた簡易サーバから、GenServerへと一般化、どのように処理しているのか?をボトルネックを解消していくという話の流れのなかで説明していっています。 サーバの基本的な役割は以下 Spawn でプロセスを分ける loopでプロセスを回す processの状態を管理する messageに反応する 送信元に応答を返す(送る) 最終的にはGenServerに近づいていくので、大きくここら辺は割愛。 ただ、 非常に考え方は重要 だし、ここら辺がErlang/Elixirのアーキテクチャとして選ぶ価値があると判断される箇所だと思うので、ちゃんと理解する方が良さそうです。 Elixir/Erlangのサーバで重要な役割を持つのは以下。 gen_server init, handle_call, handle_castなどのcallbacksを持つ 想定していない処理を無視するために、 def handle_info(_, state), do: something といった処理も入れる superevisor errorハンドリングやリカバリを担う application gen_event gen_fsm single processで処理される時のボトルネックは、多くのリクエストが溜まるにつれて応答(process)が遅くなること。 そこで、concurrentに処理できるように拡張する。ただ、cncurrentに処理できるように spawn により別プロセスで処理可能にすると、同期的に処理したい時が複雑になる。そこで、Elixir/Erlangでは以下のように GenServer.reply を使い、うまいこと非同期処理を利用している。 single processでリクエストを操作し、その実際の処理は子プロセスに実施させる。必ずこの方法が良いというわけではないが、本書ではこの方法を解の1つとして紹介していた。 ボトルネックの話でいうと、databaseを相手にしはじめると、DBとアプリをつなぐ間のpoolを処理するためのプロセスも関係してきますが、そこら辺はEctoでは poolboy が役割を担っているそうな。 poolboy Getting Startedを読んでなくていきなりこれでは辛いけれど、Getting Startedの後なら特にconcurrencyやfault toleranceの話を知る上ではこれは読んで価値ありそうです。特に、Erlang/Elixirに限らず、concurrencyやfault toleranceの考え方は参考になると思います。 補足コード Process.registerは以下…More
[Elixir in Action]BEAMとしてのprincipleとその実現のための処理系
principle against Erlang Minimize, isolate, and recover from the effects of runtime errors (fault torelance) Handle a load increase by adding more hardware resources without changing or redeploying the code (scalability) Run your system on multiple machines so that others can take over if one machine crashes (distribution) それらを達成するために、BEAM VMの処理系が作られてきました。 BEAMのprocessはOSのprocessではない OSのthreadがBEAMのSchedulerを持つ BEAMのSchedulerが、BEAMのprocessを管理する BEAMの1processの初期メモリは1~2KBなので、OSの1processのMByte単位のメモリ使用量に比べて遥かに小さいし、論理的にはBEAMのprocessは約26,8百万のprocessを実行できる BEAMのprocessは、scalabilityを達成するように、資源の追加に対して自動でその資源を使うようになっている…More
[Elixir in Action]polymorphismで拡張していく
Data abstractionsに書かれている章でした。 ここが終わったので、いよいよElixir/Erlangの真骨頂であるHigh Availabilityなシステムを構築するための説明に入ります。 structure moduleは、抽象データを作るために使われる。 map structs mapとstructsは同じように使われることが多い。 ただ、違うところもある。 例えばパターンマッチ このように、structはmapの特殊な形として使われる。 ちなみに、Recordというモジュールもあって、これは主にErlangライブラリを使う時には、Recordをimportして使いましょう、というものらしい。 polymorphism Elixirでは、polymorphismは protocol によって実現される。 defprotocol ではインターフェースを定義して、 defimpl でその実装を加えていく。 例えば、 to_string を独自のmodule、 Sample に加えるなら、 とすると、その結果として と得られる。(IO.putsはString.Chars.to_stringを指している) このprotocolは、他にも Inspect や Access といったものもある。 Elixirでは、このように元となる protocol に対して defimpl 内の実装を増やすことで機能を拡張していく。More
[Elixir in Action]Guard clauseの優先度
control flowの章の、 [Elixir in Action]Erlang/Elixirの再帰計算におけるnon-tail recursionとtail recursion 以外のことを。 以下のように、文字列バイナリも左辺/右辺のマッチングによって分けることができます。 以下のように、連続したマッチングもできます。 以下のように書いた場合、パターンマッチングでは上から順にマッチングを試みます。そのため a(other) が最も先頭にきたらそれがマッチングされます。 Elixirでは、以下のようなGuard clauseによっても適用される関数を条件付けすることができます。この時、 when で使われる型には優先度が存在します。 優先度は以下。 つまり、上記の Sample.a/1 に対してatomの :not_a_number を適用した場合、 が適用されます。エラーはありません。 そのため、数字だけを受け付けるようにするために以下のように書きます。 これで、 :not_a_number のようなものは FunctionClauseError がraiseされるようになります。 Cmprehensionsは、 for x <- [list], do: {何か} で構成される。 into などの要素を使ったりすると、繰り返しをより簡潔にかけたりする。なるほど。 Streamの説明もありましたが、Stream、色々できそうですね。 File.stream! でStreamにした後にごにょごにょStreamで操作した最後にto_list などでlistにして処理をする、といった例が載ってましたが、イマドキぽい感じがします 🙂More