Elixir
ElixirDay 24

Erlang Factory SF Bay Area 2014 の報告

More than 1 year has passed since last update.

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_assertwhen ガードにマッチングしなかった場合、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 さんです。