Elixir Advent Calendar 2014 24日目。
今年の3月に、サンフランシスコで開催された Erlang Factory SF Bay Area 2014 に行って参りました。自費で。(Thanks to 力武さん.)
その情報共有を、今年中にやっておきたいと思っていましたので、この場を借りて報告致します。
はじめに
Erlangのイベントですが、今回は鳴り物入りでElixirのセッションがいくつかありました。というか、キーノートの一つ、そして今回の目玉がElixirという、Erlangコミュニティにとっても特別な年だったのではないでしょうか。
私は、原則として複数進行するErlangのセッションのうち興味のあるテーマを選んで参加していましたが、Elixirのセッションがある場合は、そちらに参加しました。
また、会場となっているホテルのホールで開催された懇親会にも参加し、色んな方と話をしてきました。
現地で会話をした人々
Jose Valim
説明不要ですね。開場前に見かけたので、日本でのElixirコミュニティがアツいという話をしてきました。
日本人Elixirプログラマに関しては、 @mururu さんが一番印象的のようでした。さすがです。
Dave Thomas
言わずと知れた、 Programming Elixir の著者の方で、Ruby界隈では有名な方だと思われます。
Elixirに関しては、仕掛人の一人と言っても過言ではないでしょう。
イベントでは、Jose と二人でキーノートスピーチを行い、Erlangコミュニティに対してメッセージを発信しました。(「ErlangはRubyやElixirのように敷居を低くすべき」という内容で、現場では物議を醸しましたが、それだけ強いメッセージでした。)
Loïc Hoguin
Cowboyの方です。名前を呼ぶ時も「カウボーイさん」で通じます。
とてもまじめで、寛容な方であると、話をしていて感じました。
Kenji Rikitake
このイベントに参加できたのは、そもそも力武さんに誘って頂いたためです。
力武さんはスピーカーとして登壇するため、ディスカウントの権利を頂きました。
ちなみに、私がErlangに強く興味を持ったのは、2009年前後(?)に、Erlangの勉強会に参加して、力武さんの発表を聴いたことがきっかけです。
その他
Erlangのトレーナーをされている方や、その他Erlangハッカーたちと、懇親会で話をしてきました。
Elixirセッション
Elixirのセッションのうち、私は以下の3つに参加しました。
- Bitcoin & Elixir -- Living the Cutting Edge
- Pipe Dreams -- Getting More out of Elixir Pipes
- Write Less, Do More (and Have Fun!) with Elixir Macros
Bitcoin & Elixir -- Living the Cutting Edge
登壇者は、 Yurii Rashkovskii。Bitcoinのサービス開発の話です。
アーキテクチャは非常にシンプルで、bitcoind ノードがプロキシとして外部とのやりとりを行う。
また、Bitcoinのプロトコル・パーサーを全てElixirで実装したとのこと。これは、バリデーターのために使ったり、トランザクションの監視に使ったりするとのこと。
Elixirの利点は、コントロールとダイナミズムが向上したこと。欠点としては、ブロックが複雑であることと、Elixirの仕様が変わっていったため、ライブラリが陳腐化していったことを挙げています。
確かに、Elixirはv0.5.0から使っているということで、仕様変更の観点で、本当に苦労は大きかったと思います。
以降はほとんど、Elixirそのものではなく、このサービス開発の話。Dockerを使ったなど。なかなか2014年3月上旬でDockerをプロダクションで導入している組織は少なかったのではないでしょうか。面白いのですが、今回は割愛します・・・。
Pipe Dreams -- Getting More out of Elixir Pipes
サブタイトルは、 pipes |> macros |> beautiful_code
です。
登壇者は、Seven Languages in Seven Weeks の Bruce Tate。
なぜパイプなのか?
Bruceは、以前 Jose と話した際に、「なぜElixirにパイプを入れたのか?」と聞いたら、「F#からパクったんだよ」と Jose は答えたとのこと。
Bruce は続けて、「もっと教えてくれ!」と言ったら、「当初自分は大したことは考えていなかったが、関数型の思考方法を取るにあたって根本的なツールだと多くの人が考えるようになった。複数のステップを踏んだデータの変換は、関数型プログラミングの主要な考え方の一つだが、パイプ演算子はそれを具現化してくれるため、まもなく開発者に支持されるようになった。」と答えたそうです。
また、 Dave Thomas も Programming Elixir のサブタイトルにパイプ演算子を使っているし、Erlang の作者である Joe Armstrong も、Elixir のパイプ演算子を評価しています。
Phoenix Web Framework の作者である Chris McCord も、「パイプとマクロが、私がここにいる理由だ」と言っています。
関数型言語としてパイプが重要なツールであり、その機能とスタイルが多くの人に愛されていることが良く分かりますね。
pipe_matching
課題を一つ提起しています。「信頼できないタスクを表現したい」と。例としては、ロシアンルーレットを行うプログラムです。
defmodule RussianRoulette do
def click(acc) do
IO.puts "click..."
{:ok, "click"}
end
def bang(acc) do
IO.puts "BANG."
{:error, "bang"}
end
end
{:ok, ""} |> click |> click |> bang |> click
当然、
click...
click...
BANG.
click...
と表示されるのですが、これは分かりきったことです。当たり前ですね。
しかし、弾が発射された後にまた引き金を引いているので、ロシアンルーレットとしてはおかしいです。
この問題を解決するには、関数を壊すなりラップするなり何とかしないといけないわけですが、Elixirなら、 |>
演算子をマクロで変更してしまえばよいのです。Bruce は、上記のコードをこう変更しました。
pipe_matching {:ok, _},
{:ok, ""} |> click |> click |> bang |> click
ここから先は、どうやってこの pipe_matching
を実装したかの話です。
基本的な戦略としては、Pipeモジュールを定義し、そこにマクロを定義していくという形です。
defmodule Pipe do
defmacro __using__(_) do
quote do
import Pipe
end
end
defmacro pipe_matching(expr, pipes) do
quote do
pipe_while( &(match? unquote(expr), &1),
unquote pipes)
end
end
defmacro pipe_while(test, pipes) do
Enum.reduce Macro.unpipe(pipes), &(reduce_if &1, &2, test)
end
defp reduce_if(x, acc, test) do
quote do
ac = unquote acc
case unquote(test).(ac) do
true -> unquote(Macro.pipe((quote do: ac), x))
false -> ac
end
end
end
end
マクロを書くにあたって難しいのは、スコープを常に意識するということです。
スコープには2つあって、一つはコンパイルタイム・スコープ。もう一つはランタイム・スコープ。
上記 pipe_matching
で言うと、コンパイラから見えるのが、
defmacro pipe_matching(expr, pipes) do
expr
pipes
end
の部分。それ以外がランタイム・スコープ。
この2つを理解するのが重要であるという話でした。
ちなみに、上記のモジュールを使うと、
click...
click...
BANG.
となるようです。
pipe_with
例えば、
works |> works |> breaks |> works
を実行したら、 breaks
の部分で、非定型の例外処理を行いたいという問題です。
ある時は 例外を上げるし、またある時はタプル {:error, ...}
を返すということです。
基本的な戦略としては、実行コードは
use Pipe
game = pipe_with &ExceptionWrapper.wrap/2,
Roulette.start |>
Roulette.click |>
Roulette.click |>
Roulette.bang |>
Roulette.click
という形で、 pipe_with
でラッピングするということでした。
ラッパーの最終形としては、以下のようになります。
defmacro pipe_with(fun, pipes) do
Enum.reduce Macro.unpipe(pipes), &(reduce_with &1, &2, fun)
end
defp reduce_with(segment, acc, outer) do
quote do
inner = fn(x) ->
unquote Macro.pipe((quote do: x), segment)
end
unquote(outer).(unquote(acc), inner)
end
end
この際も、コンパイルタイム・スコープとランタイム・スコープを意識する必要があるとのことでした。
非定型の例外処理を行うモジュール ExceptionWrapper
は、こんな感じです。
defmodule ExceptionWrapper do
def wrap({:error, e, acc}, _), do: {:error, e, acc}
def wrap(acc, f) do
f.(acc)
rescue
x in [RuntimeError] ->
{:error, x, acc}
end
end
最後に
- プログラミングをすることは考えること
- パイプは私たちが考えることを助けてくれる
- マクロはパイプをより良くしてくれる
とのことでした。
Write Less, Do More (and Have Fun!) with Elixir Macros
Phoenix Frameworkでおなじみの @chris_mccord さんのセッションです。
いきなり「マクロは書くな」というセンセーショナルな話から始まりました。Elixirの長所の一つがマクロだと僕は思うのですが・・・。
そして「マクロをむやみに使え」。えっと、どういうことでしょう・・・。
マクロとは何か
コードを記述するコードです。Elixir自体が根本的にマクロで記述されています。例えば、if, unless, cond, def, defmodule 、これらは全てマクロです。マクロはコンパイル時にElixirのコード全体に対して適用され、Elixir本来のコードとなります。
defmodule でモジュール定義するのと同様、quote による定義は、Elixirのデータ構造として表現されます。
quote
iex> quote do: (1 + 2) - 5 * 10
{:-, [context: Elixir, import: Kernel],
[{:+, [context: Elixir, import: Kernel], [1, 2]},
{:*, [context: Elixir, import: Kernel], [5, 10]}]}
quote は、いかなる式でも、3つの要素をもつ一連のタプルとして表現することができます。
3つの要素について、最初は Atom、次がメタデータ、最後が関数コールの引数です。
Chrisは、このような例を示しました。
defmodule Assertions do
defmacro assert({operator, _, [lhs, rhs]}) do
quote do
do_assert unquote(operator),
unquote(lhs),
unquote(rhs)
end
end
def do_assert(:==, lhs, rhs) when lhs == rhs do
IO.write(".")
end
def do_assert(:==, lhs, rhs) do
IO.puts """
FAIL: Expected: #{lhs}
to be equal to: #{rhs}
"""
end
def do_assert(:>, lhs, rhs) when lhs > rhs do
IO.write(".")
end
def do_assert(:>, lhs, rhs) do
IO.puts """
FAIL: Expected: #{lhs}
to be greater than: #{rhs}
"""
end
end
defmodule MyTest do
import Assertions
def run do
assert 1 == 1
assert 3 > 1
assert 5 == 0
assert 0 > 8
end
end
これを実行してみます。
iex> MyTest.run
..
FAIL: Expected: 5
to be equal to: 0
FAIL: Expected: 0
to be greater than: 8
:ok
assert
と ==
又は >
の組み合わせには、マクロで新たな機能が追加され、do_assert
の when
ガードにマッチングしなかった場合、FAIL
で始まるメッセージを出すようにしました。
use とか unicode とか
前々回の私の記事で継承として紹介した use
もまた、マクロです。
ElixirのUnicode関連の話もありましたが、かなり深くて、しかもややこしいので、ここでは割愛します。
Phoenix Web Framework
Router DSLは、このように記述されます。
defmodule Router do
use Phoenix.Router, port: 4000
get "/pages/:page", Ctrls.Pages, :show, as: :page
get "/files/*path", Ctrls.Files, :show
post "/files", Ctrls.Files, :upload
resources "users", Ctrls.Users do
resources "comments", Ctrls.Comments
end
end
これ、全部マクロです。
get "/pages/:page", Ctrls.Pages, :show, as: :page
この行は、
def match(conn, :get, ["pages", page]) do
conn = conn.params(Dict.merge(conn.params, [
{"page", page}
]))
apply(Ctrls.Pages, :show, [conn])
end
このようなコードに展開されます。
Fun
というわけで、むやみやたらにマクロを使って作られたのが Phoenix Web Framework です。
最後に、GitHubのリポジトリの "watchers" を取得するコードを書いて「楽しい!」で締めくくりました。
結局、冒頭の「マクロは書くな」というメッセージはよくわかりませんでしたが、「マクロをむやみに使え」「そして楽しめ」(and Have Fun!)というのは、発表者Chris が Phoenix で体現していることというわけですね。
まとめ
Erlang Factory SF Bay Area 2014 の報告をしました。
登壇者はいずれもレベルが高いですし、学びも非常に多く、良い刺激になりました。
ぜひ2015年も行きたいです。(お金があれば・・・休みが取れれば・・・)
明日、今年のラストを飾るのは @ma2ge さんです。