Elixir試飲録 (2) – カルチャーショックに戸惑う: 並行指向プログラミング

昨夜、Dave Thomas氏の「Programming Elixir」を読み終えて、こうしてブログを書いている。

読み終えて「とんでもないところに足を踏み入れたな」と思う。漂うのは、何もかもが今までのプログラミングモデルとは違うという圧倒的なアウェイ感である。

Dave Thomas氏は度々このブログにも登場しているが、『達人プログラマー』の著者であり、アジャイルソフトウェア開発宣言の発起人の一人でもある。近年はRubyの人という感じであったが、ElixirにRuby以来の衝撃を受けて、この「Programming Elixir」を執筆するに至ったとのこと。

この本が素晴らしいのは、元々はオブジェクト指向のプログラマーであった著者が関数型言語であるElixirに出会って、徐々にそのコンセプトに体を慣らせて行く過程を、読者が追体験できるところである。この本で扱っているのは Elixir やその基礎となっている Erlang/OTP のほんの導入部分のみであり、主眼はオブジェクト指向プログラマーが関数型あるいは並行指向プログラミングへとパラダイムシフトするための手助けをするといった趣向になっている。

「Programming Elixir」のサンプルプログラムを動かしながら一通りのトピックを見て回り、Elixir(というかErlang)で最も重要なのは、関数型プログラミングではなく、「並行指向プログラミング」と呼ばれる、そのプロセス管理の仕組みであることが分かった。関数型言語はあらゆるプラットフォームで利用出来るが、この並行処理のモデルはおそらくErlangにしかないものなのだろう。

Elixirの並行処理の威力を見るにはサンプルコードを実行するのが手っ取り早い。

まず、Elixirのサイトにある一番始めのサンプルコード(Hello world)が、いきなり並行処理の例になっている。

parent = self()

spawn_link(fn ->
  send parent, {:msg, "hello world"}
end)

receive do
  {:msg, contents} -> IO.puts contents
end

これを hello.exs というファイルに保存して、

$ elixir hello.exs 

のように実行すると、

hello world

となる。

Elixirを知らない人でも、このコードが何をしているかは、何となく分かるのではないだろうか。

  1. spawn_linkという関数に渡された関数が、このプログラムを実行しているメインのプロセスとは別のプロセスとして起動されて、関数の内容を実行する。
  2. 新しく作られたプロセス側では、メインプロセス側(parent)に “hello world” というメッセージを送る。
  3. メインプロセス側は、どこからかメッセージが来ないかを待ち受けて(receive)、メッセージが来たらそれをコンソールに表示する。

このように、spawn でプロセスを作り、sendreceive でメッセージのやり取りをするという単純な仕組みである。プロセスの間でのやり取りはこのメッセージ交換だけで、可変データを共有するということがないので、従来の並行処理について回る排他処理などが必要にならないというのが Elixirの強みである。

この例では一つのプロセスを作っただけなので、他の言語でも同様のことは簡単に出来るかもしれない。では、以下の例だとどうだろうか。

defmodule Chain do
  def counter(next_pid) do
    receive do
      n -> send next_pid, n + 1
    end
  end

  def create_processes(n) do
    last = Enum.reduce 1..n, self,
      fn (_, send_to) -> spawn(Chain, :counter, [send_to]) end
    send last, 0
    receive do
      final_answer when is_integer(final_answer) ->
        "Result is #{inspect(final_answer)}"
    end
  end

  def run(n) do
    IO.puts inspect :timer.tc(Chain, :create_processes, [n])
  end
end

いきなり複雑な例になったが、やっていることは単純である。run関数が呼ばれると、指定された数(n)だけのプロセスを次から次に立ち上げて、それらのプロセスには順番に次のプロセスへの参照を持たせて数珠つなぎのような構造にする(終端はメインプロセス)。全てのプロセスを立ち上げたら、先頭に数値「0」を送ると、プロセスの間でバケツリレーをしながら、その数値に「1」を加えて行く。最後はメインプロセスに到達して結果の値をコンソールに書き出す。この一連の処理は、Erlangの関数である :timer.tc によって時間が計測されており、結果とともに経過時間も出力される。

さて、このコードを chain.exs というファイルに保存して、以下のように実行したらどうなるだろうか?

$ elixir --erl "+P 1000000" -r chain.exs  -e "Chain.run(1_000_000)"

生成するプロセスの数はなんと100万個である。デフォルトのセッティングでは同時並行プロセス数の上限に達してしまうので、オプション(--erl "+P 1000000")で上限を変更している。

これを筆者のPC(Macbook Pro – 3 GHz Intel Core i7, 16GB RAM)で実行した結果が以下である:

{7011658, "Result is 1000000"}

一つ目の数字の単位はマイクロ秒なので、実に100万個のプロセスを起動して連携させる処理がたったの7秒程度で終わったということになる。

何故このようなことが可能なのかと言えば、これらのプロセスがOSで管理されているプロセスやスレッドなどではなく、Erlang VMの中で管理されている独自のプロセスだからである。このErlangが提供する極めて軽量なプロセスモデルが、Elixir/Erlang環境が単なるプログラミング言語以上のインパクトを持つ所以となっている。

このようにElixirでは、一つのプロセスが極めて軽量なため、一つのプロセスを作るのは、オブジェクト指向システムで一つのオブジェクトを作るのと同じぐらいの感覚であると「Programming Elixir」では説明されている。実際プロセスの中には状態を持つものもあり、それらがメッセージでやり取りをするとなれば、モデル的にはオブジェクトと変わりないように思える。Elixirでは、状態を持つプロセスを「Agent(エージェント)」として容易に管理する仕組みがあり、これは、かつてポスト・オブジェクト指向の有力候補と呼ばれていたエージェント指向ではないかと、はたと思い当たったが、それは多分違うような気がする。

Elixir試飲録 (3) – マルチコア危機によるパラダイムシフト: オブジェクト指向から並行指向へ

Elixir試飲録 (1) – 今、プログラミング言語を選ぶということ: Scala, Go, Elixir

たまチームでは、これまでJava系の言語・環境を使ってWebサービスを開発してきた。言語で言えば、Java, Scala, Groovyなどであり、フレームワークで言えば、Spring, Play, Scalatra, Grailsなどである。しかし、来年から新しいプロジェクトを始めるに当たって、心機一転、新しい環境で頑張ってみようということになり、年の瀬も押し迫った今、少しずつ準備を進めているところである。

ここ数年のWeb開発三大勢力と言えば、Ruby on Rails, PHP, Java (JVM) という感じになるだろうか。これらに共通する大きな枠組みは「オブジェクト指向」である。Web開発に限らず、オブジェクト指向はかれこれ20年以上ソフトウェア開発の主流だったと思うが、数年前から徐々に「関数型」という考え方が職業プログラマの間にも浸透してきて、最近では無視することの出来ないムーブメントになりつつある。

2015年の今、関数型プログラミングの流行を牽引している最有力の言語はおそらくScalaだろう。ScalaはJVM上で動くプログラミング言語で、オブジェクト指向と関数型双方の考え方を取り入れたハイブリッドなデザインになっている。オブジェクト指向に慣れ親しんだプログラマが、Javaより簡潔に書ける、いわゆる「Better Java」として使い始めることができ、必要に応じて関数型のプログラミングを取り入れることも出来る。アーリーアダプターの評価も高く、TwitterがRubyからScalaに移行した事案も話題になった。Scalaはたまチームの開発でも有力な選択肢の一つである。

しかし最近、Scalaに関して、このようなブログ記事が話題になった。

著者は CrowdStrike社でテクニカルディレクターを務めるJim Plush氏。CrowdStrike社での主要な開発言語をScalaからGo言語に切り替えたという話である。

Plush氏によれば、Scalaプログラマーは以下の二つの派閥に分かれる傾向があるらしい:

  1. Scalaを「Better Java」として使い、関数型の機能はおまけ程度に考えていて深く追求はしない。
  2. Scalaを関数型言語として扱い、より深く関数型のコンセプトを追求する。
Scala developers typically travel down two paths

小規模のチームであれば、それほど大きな問題にはなり辛いが、プロジェクトが成長して規模が拡大すると、これらの派閥間のすれ違いが無視出来なくなるという。端的に言えば、関数型追求派のプログラマーの書いたコードを、Better Java派はメンテナンス出来なくなる。記事の事例では、緊急に対応しなければならない障害が発生して、現場にいたScalaプログラマーがコードを見ると意味不明な記号が使われている。IDEやGoogleの力を借りてもなかなか解読出来ない、コードを書いた本人はバカンス中である。というわけで、数分で解決出来るような些細な問題に数時間を要したという。このような問題を目の当たりにして、主要言語としてはもっと開発要員の確保しやすい手続き型言語のGoに移行することにしたという経緯である。

ちなみに筆者は、関数型の経験がほとんどない(ちょっとScalaをかじった程度の)、重度のオブジェクト脳プログラマーであるが、Plush氏が指摘している問題については、自分がこれまでぼんやりと感じていたこととそれほど相違はないと感じた。

まず前提として、当たり前のことではあるが、どのようなプログラミング言語でも読み辛く、あるいは分かり辛くコーディングすることは可能である。ただ、手続き型やオブジェクト指向は、コーディングスキルが向上するほどコードが読みやすくなっていくように思えるのに対して、関数型はスキルが高いほどコードは難解になっていく印象がある。単に自分がオブジェクト脳だからという可能性も否定出来ないが、関数型の抽象性の問題ではないかと個人的には考えている。つまり、オブジェクト指向が日常的な抽象性をベースにしているのに対して、関数型は数学的な抽象性、つまり日常的な抽象性の延長としては理解し難い領域の抽象性を扱っているからではないかと推測している。この辺もこの連載で追求して行けたらと思う。

しかし、こういった関数型プログラミングに対する印象も、一度も実プロジェクトで使ったことがなければ、単なる表面的な思い込みになってしまう。いずれはきちんと取り組んでおかなければと考えていたところに遭遇したのが以下の記事だった。

Elixirは、今から三年ほどまえの2012年に最初のバージョンがリリースされ、そして去年、v1.0 がリリースされたばかりのかなり新しい言語である。このブログを読んでいる方なら耳にしたことのある方が多いかもしれない。

Elixirは、Erlangのvirtual machine(BEAMと呼ばれている)上で動く言語で、ErlangとElixirの関係は、丁度JavaとScalaの関係に似ている。Erlangという言語とその環境については度々耳にしていたが、一部のハイスケーラビリティを要求される特殊な世界の話で、なんとなく自分とは関係のない別世界の話のように感じていた。ところが件の記事を読んで、Erlang環境上でWebアプリケーションを作る基盤が整ってきていることを知り、俄然興味が湧いてきたわけである。

Elixirを作ったのは、Ruby on Rails開発チームのコアメンバーである Jose Valim氏である。だからなのか、Elixirはいわゆる関数型言語であるが、Scalaのようなハードコアな感じはない。言語のシンタックスを見る限り、もっと気軽に関数型プログラミングを始められそうな親しみやすさが漂っている。更に言えば、Railsコミュニティとその周辺でよく聞かれる「一番大事なのは目の前の仕事を片付けることである」という哲学を踏襲しているように感じられる。

一方、Erlangは、スウェーデンの通信機器メーカーであるエリクソンが1980年代に開発した言語環境で、元々は電話交換機のプログラム用に開発されたということである(件の記事によれば、今でもモバイルの4Gネットワーク上で通信すれば、Erlangプログラムが動いている機器を経由する可能性があるらしい)。電話のネットワークは、当然のことながら高い耐障害性を求められるし、常に誰かが通話しているので、ネットワーク上のプログラムをアップデートする度にネットワークを遮断するわけにはいかない。というわけで、そのような特殊なニーズに基づいて以下のような要件を満たす言語環境が設計された。

  • 分散可能
  • 耐障害性(フォルトトレラント)
  • リアルタイム性
  • 無停止

Erlangはこれらの要件を極限まで追求しているため、プログラムを稼働したまま更新出来る「ホットスワップ」と呼ばれる機能を始めとして、いわゆるこれまでのメインストリームの言語環境と比較してもかなり特殊な進化を遂げている。

アメリカのメッセンジャーアプリの最大手 Whatsappでは、一つのサーバーで200万同時接続を実現するシステムをErlangで実装しているという。高度なリアルタイム性と大量の同時接続を要求するオンラインゲームの世界でもErlangは活躍しており、Call of Duty や Game of War といったゲームで利用されているということだ。

Erlangは、これほどの歴史と能力を持ちながら、その言語や環境の特殊性でメインストリームの開発環境とはなってこなかった。そこに一石を投じたのが Elixirである。次回以降では、上に挙げたErlangの特徴について、Elixirの観点から掘り下げながら、この新しいプラットフォームについての理解を深めて行きたいと思う。

Elixir試飲録 (2) – カルチャーショックに戸惑う: 並行指向プログラミング