読者です 読者をやめる 読者になる 読者になる

7つの言語 7つの世界を読んだ

「7つの言語 7つの世界」という本を図書館から借りてきて読んだ。この本では、異なるパラダイムをサポートする7つの言語について、シンプルながら本質を捉えた説明がなされていた。もともと色んな考え方を知るのは好きな方なので、めちゃめちゃ楽しんで読めた。

取り上げられていた7つの言語

筆者が選んだのは、Ruby, Io, Prolog, Scala, Erlang, Clojure, Haskellの7つの言語だった。筆者としては、オブジェクト指向が圧倒的に広まってる中、関数型の考え方や並行処理に対する優れたアプローチを紹介したいという意図があったようだ。

7つの言語のうちRuby, Scala, Haskellについてはそこそこ触った事はあったんだけど、他は全然知らない言語だった。特にIoについては名前すら知らなかったので、この本のお陰で出会えて良かったと思う。

ClojureLisp族だし置いとくとして、Io, Prolog, Erlangみたいな全然触れた事が無かった言語についてまとめてみる。

Io

Ioはプロトタイプベースのオブジェクト指向言語で、そういった点ではJavaScriptの仲間になる。プロトタイプベースのアプローチでは、オブジェクトの振る舞いをクラスによって表現するのでは無く、プロトタイプとなるオブジェクトを指定することで表現する。JavaScriptのプロトタイプは、関数をコンストラクタにして、コンストラクタのprototypeプロパティにオブジェクトを指定して、そのプロトタイプを持つオブジェクトをnew演算子で生成して...みたいにめちゃめちゃ分かりにくい設計になってるんだけど、Ioはかなりシンプルな文法でプロトタイプベースのオブジェクト生成が行えるので、分かりやすさという意味でめちゃめちゃ魅力的だった。

Ioでは、オブジェクトを生成する際は基本的には既存のオブジェクトをCloneすれば良い。

Ferrari := Car clone
// CarオブジェクトをプロトタイプとしたFerrariオブジェクトを生成

cloneがCarオブジェクトに対するメッセージになってるのもポイントで、Ioでは特殊な構文というものを用意せず、徹頭徹尾メッセージングによってプログラムを構築する事になる。この辺は以下の記事がよくまとまってると思う。

るびま: Rubyist のための他言語探訪 【第 3 回】 Io

一貫性がある設計というのは自分としてはものすごく好ましい事で、理解しやすいし興味深かった。

並行処理を言語の基盤レベルでサポートしてるのも特徴的で、Ioではコルーチン、アクター、フューチャといった機能を簡単に使うことが出来る。コルーチンはメソッドの処理中にいったん処理を抜けて他のメソッドに処理を譲る仕組みで、Ioにおいてはメッセージの非同期呼び出しとyieldメッセージでコルーチンを実現することが出来る。

アクターは分散処理を実行する単位となる存在で、Ioにおいては独自のThreadを持つオブジェクトとして表現される。アクターの生成はものすごく簡単で、オブジェクトに非同期メッセージを送るだけで良い。

  1 obj1 := Object clone
  2 obj1 name := "obj1"
  3 obj1 test := method(
  4   for(n, 1, 3,
  5     n    print
  6     ": " print
  7     name println
  8     yield
  9   )
 10 )
 11
 12 obj2 := obj1 clone
 13 obj2 name := "obj2"
 14
 15 obj2 @@test; obj1 @@test
 16 Coroutine currentCoroutine pause

上記の例では、obj1とobj2に@@testという非同期メッセージを送って、アクターとして動作させている(メッセージの先頭に@@をつけると非同期呼び出しになる)。

testメソッドの中にyieldメッセージが含まれているため、この時点で処理を別のアクターに移すことになる。これはまさにコルーチンとしての振る舞いである。

上記のコードの実行結果は、以下のようになる。

$ io actor.io
1: obj1
1: obj2
2: obj1
2: obj2
3: obj1
3: obj2
Scheduler: nothing left to resume so we are exiting

別々に@@testメッセージを呼び出したobj1とobj2が、yieldで処理を譲り合うことで順番に出力を行っている事が分かる。

Ioにおいてはフューチャと呼ばれる機能も用意されていて、これは非同期呼び出しの「結果」を即座にオブジェクトとして利用出来る仕組みとなっている。メッセージ呼び出しに@をつけると結果を返す事が出来て、この時帰ってくるのがフューチャである。

  1 futureResult := URL with("http://google.com/") @fetch
  2
  3 writeln("Do something immediately while fetch goes on in background...")
  4 writeln("fetched ", futureResult size, " bytes")
  5 writeln("end")

上記のコードの実行結果は、

$ io future.io
Do something immediately while fetch goes on in background...
fetched 9367 bytes
end

となる。

ポイントは"Do something immediately while fetch goes on in background..."という出力が即座に行われる点で、これは1行目の@fetchの非同期呼び出しが別Threadで処理されて、現在のThreadでは即座に次の処理が行われる為である。4行目でfutureResultを使おうとしている為、その時点で@fetchの処理が終わってなかった時に初めて処理がブロックされる。特殊な事をしなくても、非同期に呼び出しておいてその結果を必要になったタイミングで使えるので使い勝手が良さそうである。

いったんまとめ

Ioだけで結構長くなったのでいったんまとめる。Ioでは、メッセージングとその非同期呼び出しというシンプルな構造で、コルーチン、アクター、フューチャといった便利な機能を実現している。並行処理についてはこれまであんまり意識した事が無かったけど、イメージをつかめた気がする。

どうでも良い話だけど、coroutineを知った事でgoのgoroutineという名前の意味もやっと分かった気がする。

goroutineはgo文で関数を実行する事で非同期に起動される手続きで、channelを使って値をやり取りできる。channelは値の受信時に処理をブロックする。その為、channelを使うことでgoroutine同士の同期をとる事が出来て、coroutineとしての機能を実現する事も出来る。そこから、coroutineっぽい名前(goroutine)がつけられたんだと思う。

ちょっと調べたら

"Users of C#, Lua, Python, and others might notice the resemblance between Go's generators/iterators and coroutines. Obviously, the name "goroutine" stems from this similarity"

っていう記述を見つけたから間違いなさそう。

Go Language Patterns: Coroutines

Prolog, Erlangについて

Prologは論理制約プログラミグが出来る言語で、制約の元で非決定性計算を行うっぽい。ErlangPrologを出発点としてるからシンタックスは似てるけど、一番の強みはそこじゃなくて「平行性と耐障害性」らしい。Threadでもプロセスでも無い「軽量プロセス」という単位で大量の平行処理を行わせる事ができて、メッセージングによって協調的な動作をさせたり死活監視や再起動を簡単に行えたりする。これがものすごく良いらしくて、熱く語ってるブログも見つけた。

なぜErlang/OTPなのか

実は元々Erlangに一番興味があって「7つの言語 7つの世界」という本を借りたので、良い勉強になった気がする。

これらの言語については、また今度詳しくまとめるかもしれない。

7つの言語 7つの世界

7つの言語 7つの世界