トップ «前の日記(2007-06-21) 最新 次の日記(2007-06-23)» 編集

じじぃの日記、ツッコミ可

Twitter: @jijixi_org
Xbox Live: jijixi

初心者が書いた OCaml 入門
Spotlight tips サイト内リンク集
1970|01|02|
2003|10|11|12|
2004|01|02|03|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|11|
2011|05|
2012|01|

2007-06-22 [長年日記]

% [Scala] Actor 初体験

ふとしたきっかけでサンタクロース問題を書いてる人を見付けて、つい対抗意識(?)を燃やしてしまった。 や、単に Actor 使って何か書く口実が欲しかっただけだけど(苦笑

ともあれ、この前リングベンチマークのコードを読んでるときにも色々疑問があったりしたんで (receive と react の違いって何?とか)、チュートリアルも眺めつつ。

% cat santa.scala
import scala.actors._
import java.lang.Thread
import java.lang.Math

object Santa extends Application {
   def waiting() = Thread.sleep((Math.random() * 5000).toLong)
   def reindeer(santa:Actor):Unit = {
      waiting()
      Console.println("reindeer was returned.")
      santa ! 'return
      Actor.loop {
         Actor.react {
            case 'play => reindeer(santa)
         }
      }
   }
   def elf(santa:Actor):Unit = {
      waiting()
      Console.println("elf was visited.")
      santa ! 'hello
      Actor.loop {
         Actor.react {
            case 'bye => elf(santa)
         }
      }
   }
   def santa(reindeers:List[Actor])(elves:List[Actor]):Unit = {
      (reindeers.length, elves.length) match {
         case (9, _) =>
            Console.println("delivery...")
            reindeers.foreach(_ ! 'play)
            santa(List())(elves)
         case (_, n) if n >= 3 =>
            Console.println("meeting...")
            elves.takeRight(3).foreach(_ ! 'bye)
            santa(reindeers)(elves.take(n-3))
         case (_, _) => Actor.loop {
            Actor.react {
               case 'return => santa(Actor.sender::reindeers)(elves)
               case 'hello  => santa(reindeers)(Actor.sender::elves)
            }
         }
      }
   }

   val s = Actor.link { santa(List())(List()) }
   for (x <- List.range(0,9))  Actor.link { reindeer(s) }
   for (x <- List.range(0,10)) Actor.link { elf(s) }
}

本来は import scala.actors.Actor._ してやるのがお作法っぽいけど、慣れてないとどれがどのパッケージの関数なのかさっぱりわからんので、あえてインポートせずに書いている。 あと、Scala 的なスリープの仕方がわからなかったので、安易に Thread.sleep とか使ってたり。 ただ、これは後で書くけど、たぶんあんまり良くない。

基本的な作りはこの前書いた JoCaml 版と同じ。 違うのは、JoCaml の方ではトナカイやこびとのプロセスはサンタのところに来ると終了していて、事が済んだときにサンタが新たに減った分を補充していたのが、こっちでは各プロセス (アクター) は終了せずに、サンタが (事が済んだときに) メッセージを送ってくるのを待つようになっている点。 まあ、完全に JoCaml 版と同じにしても良かったんだけど、それはそれでつまらないので。 こっちの方が Erlang (アクターモデル) っぽいし。

実行するとこんな↓感じ。 コンパイルすると大量の class ファイルができるので、試してみるなら適当にディレクトリを用意した方が良いと思う。

% scalac santa.scala
% scala -cp . Santa
reindeer was returned.
reindeer was returned.
reindeer was returned.
reindeer was returned.
reindeer was returned.
reindeer was returned.
reindeer was returned.
elf was visited.
reindeer was returned.
reindeer was returned.
delivery...
elf was visited.
reindeer was returned.
elf was visited.
meeting...
reindeer was returned.
elf was visited.
reindeer was returned.
reindeer was returned.
elf was visited.
elf was visited.
meeting...
reindeer was returned.
reindeer was returned.
reindeer was returned.
reindeer was returned.
elf was visited.
^C

てなところで一旦終了。

% [Scala][JoCaml] Actor の簡単なまとめ

やっぱり自分で何か書いてみないとわからないこともある。 てなわけで、サンタ問題を書いたおかげで、いろいろわかったので簡単にまとめ。

  • スレッド (Java のスレッド) とアクター (Scala のスレッド) は 1:1 の関係ではない。
    • スケジューラが、あらかじめ用意された WorkerThread (デフォルトでは 4 個) にアクターを割り振る。
      • 実装を見たわけじゃないけど、きっとアクターそのものじゃなく、適当なところで分割されて関数化されたものを割り振るんじゃなかろうか。
    • WorkerThread は、全てのスレッドがブロックして、かつ、処理すべきものが残っている場合に増える (らしい)。
      • 基本的には JoCaml と似ている。(ただし、JoCaml はブロックしていなくても増える)
    • Actor を使ったときには 5 つのスレッドが増えるので、ワーカが 4 個だとすれば、もう 1 個はスケジューラか?
      • つーか、Scala のランタイム自体が 10 個もスレッド使ってるんで、4 個というのはどうもケチくささを感じるが……
  • receive はスレッドをブロックする。react はブロックしない。
    • 上記のとおり、スレッドとアクターは 1:1 で対応しないので、安易に receive でブロックしてしまうと面倒なことになりうる。
    • そのために用意されているのが react 。
    • ただ実際には Erlang の receive のようにブロックしてくれないと困るケースは多い。
    • そういうときは loop { react { ... } } を使う。react はメッセージが無いときは何もしないので、これで見た目上は receive でブロックしてるのと同じ状態に。
      • これも実装を見たわけじゃないから想像だけど、loop はブロック内のコードを一回分ずつワーカに渡すようになってるんだと思われ。

スレッドとアクターが 1:1 対応じゃないということで、JoCaml でじわじわ問題が出るシチュエーションと同じ懸念 (たまたま全てのスレッドで無限ループになったりすると、残りの処理が動かなくなる可能性がある) があるわけだけど、最初からスレッドが 4 つ用意されるので、そういう問題に遭遇する可能性は低そうではある。 JoCaml と違って、元々 1:1 で対応させる気が無いようだから、これはこれで良いんだろう。

んで、さっき書いた Thread.sleep を使うのはマズいという話だけど、まさにこの事実が関係する。 つまり、あの場面ではアクターをスリープさせたいのに、Thread.sleep を使うと実質そのアクターだけじゃなく、次に同じスレッドで動くはずのアクターまで一緒にスリープしてしまうことになるわけだ。 例えば 8 個のアクターが一律 5 秒で Thread.sleep を使ったとしても、5 秒寝るものもあれば 10 秒寝るものもあるってこと。 それはちょっとがっかりだ。 とりあえず、ざっとリファレンスを眺めた限りでは、そこを何とかするようなものは用意されていないっぽい。 いろいろ試してみたけど、結局うまい方法は見つけられなかった。

reactWithin(5000) {
  case TIMEOUT => ()
}

とかで行けるかなーと思ったんだけど、なんか scala.actors.SuspendActorException とかいう例外が出てダメ。 まあ、単純にスリープして時間稼ぎたいなんてシチュエーションは、実用的にはむしろレアケースのような気もするし、気にするだけ無駄かなあ。 JoCaml の場合は、基本的にプロセスとスレッドを 1:1 で対応付けるポリシーのように見えたんで色々手を尽くしたけど、Scala はそうじゃないっぽいし。

あと、JoCaml であの問題にこだわったのは、& で繋いでプロセスを並行起動っていう文法があるから、その分くらいは全部別スレッドで動いて欲しいというのもあった。 Scala の場合はそういうの無いしね。

% [Python] all, any とか doctest とか

odz さんの『入れ子リストの中身を順に表示』を見てたら、いろいろ知らないものが。

おぉ、all なんて関数があったのか。 日本語訳のマニュアルには見当たらなかったが……また 2.5 future か。 any ってのもあるみたいだ。 いい加減、日本語マニュアルに頼るの止めた方が良いんだろうか。 いつまで経っても 2.5 にならんし。

んで、なんか doctest ってモジュールがおもしろい。 これだけで、ちょっとしたテストフレームワークになってるし、doctest 形式のテストを unittest 用に変換したりもできるみたい。 提言のページが、なかなか含蓄がある。

よい例がたくさんの言葉に値することは多々あります。

まったくもって、そのとおりだと思う。 常々「ちゃんと動くサンプルコード重要」と思っているわしとしては、このモジュールはきっちり使いこなすべきものかもしれない。

% [Scala] サンタクロース問題改訂版

さっきのバージョンは誰がサンタのところに来るかに明らかな偏りがあったので (最初に 4 匹トナカイが帰ってくるのは、ほぼ確定)、全員がそれぞれ独自に待ち時間を決めるのはやめて、神のサイコロによって「働け」と天啓が下るようにしてみた。 ちょっと手を抜いてて、相手が何をしてるかなんておかまいなしでメッセージを送るので、仕事して戻ってみたらすでにまた「働け」コールが来ていて、とんぼ返りでサンタのところに引き返さなきゃならない…みたいな連中も出るっぽい(苦笑

% cat santa.scala
import scala.actors._
import scala.actors.Actor._

object Santa extends Application {
   val rand = new java.util.Random(System.currentTimeMillis())
   def wheelOfFortune(members:Array[Actor]):Unit = {
      val delay = rand.nextInt(2000)
      val num = rand.nextInt(members.length)
      java.lang.Thread.sleep(delay)
      members.apply(num) ! 'work
      wheelOfFortune(members)
   }
   def reindeer(santa:Actor):Unit = {
      loop { react {
         case 'work =>
            Console.println("reindeer was returned.")
            santa ! 'return
            loop { react {
               case 'play => reindeer(santa)
            }}
      }}
   }
   def elf(santa:Actor):Unit = {
      loop { react {
         case 'work =>
            Console.println("elf was visited.")
            santa ! 'hello
            loop { react {
               case 'bye => elf(santa)
            }}
      }}
   }
   def santa(reindeers:List[Actor])(elves:List[Actor]):Unit = {
      (reindeers.length, elves.length) match {
         case (9, _) =>
            Console.println("delivery...")
            reindeers.foreach(_ ! 'play)
            santa(Nil)(elves)
         case (_, n) if n >= 3 =>
            Console.println("meeting...")
            elves.takeRight(3).foreach(_ ! 'bye)
            santa(reindeers)(elves.take(n-3))
         case (_, _) =>
            loop { react {
               case 'return => santa(sender::reindeers)(elves)
               case 'hello  => santa(reindeers)(sender::elves)
            }}
      }
   }

   val s = link { santa(Nil)(Nil) }
   val rs = for (x <- List.range(0,9))  yield link { reindeer(s) }
   val es = for (x <- List.range(0,10)) yield link { elf(s) }
   link { wheelOfFortune(rs.toArray ++ es) }
}

loop と react の組み合わせは、ある意味慣用句なので、ちょっと書き方を工夫してみた。 っていうか、行数ばっか嵩んで、たまったもんじゃないし。 あと、空リストとしての List() を、存在忘れてた Nil に直したり。

% [Erlang][Scala] やっぱり Erlang の熟成度はたいしたもんだなあ

Scala の Actor では、思ったように動いてくれない以下のようなケース。

scala> import scala.actors.Actor._
import scala.actors.Actor._

scala> for (x <- List.range(0,10)) actor{Thread.sleep(5000);println(self)}
unnamed0: Unit = ()

scala> scala.actors.Actor$$anon$0@a2bb54
scala.actors.Actor$$anon$0@c76b4c
scala.actors.Actor$$anon$0@5555bf
scala.actors.Actor$$anon$0@709b64
scala.actors.Actor$$anon$0@8e7b84
scala.actors.Actor$$anon$0@4f53eb
scala.actors.Actor$$anon$0@e6b82
scala.actors.Actor$$anon$0@b45f54
scala.actors.Actor$$anon$0@46101f
scala.actors.Actor$$anon$0@cc88ce

やってみればわかると思うけど、5 秒間隔で、4 個 → 4 個 → 2 個と表示される。 主に、Actor ローカルな sleep 関数が用意されていないのが原因。 いろいろと試してみた感じだと、現状だとなかなか自前で (自前じゃなくても?) それを作るのは難しい。 4 個ずつなのは、実際に処理するのが 4 つのワーカスレッドだから。

で、同じことを Erlang でやると、

Eshell V5.5.4  (abort with ^G)
1> F = fun(_) -> spawn(fun() -> timer:sleep(5000),io:format('~p~n',[self()]) end) end.
#Fun<erl_eval.6.72228031>
2> lists:foreach(F,lists:seq(1,10)).
ok
<0.41.0>
<0.40.0>
<0.39.0>
<0.38.0>
<0.37.0>
<0.36.0>
<0.35.0>
<0.34.0>
<0.33.0>
<0.32.0>

きっちり 5 秒後に全部表示される (もちろん多少の誤差はあるけど)。 別にシステムスレッドが 10 個使われてたりもしない。 まあ、こういうのができるのは、たぶん VM まで自前だからこそのような気もする。 Scala はどうしても JavaVM の制約に縛られるんだろうし。

ちなみに、timer:sleep() の実装は、↓こんな(苦笑

sleep(T) ->
    receive
    after T -> ok
    end.

これって、まさしくさっき Scala で試した、

reactWithin(5000) {
  case TIMEOUT => ()
}

と同じようなものだと思うんだけど、こっちはうまく動いてくれないんだよなー。 かといって、receiveWithin を使っちゃうと Thread.sleep 使うのと変わんないし。

% [Scala] ちょっと不思議な def

基本的には def は関数定義で val は値の束縛なんだけど、実はどうやら、

  • val (var も同様) の場合、右辺を評価した上で束縛する
  • def の場合、右辺は評価せずに束縛する

という特徴があるらしい。 例を挙げるとこういうこと。

scala> val a = { println("hoge"); 1 }
hoge
a: Int = 1

scala> def b = { println("fuga"); 2 }
b: Int

要するに、def を使うとブロックそのものを束縛できる。 んで、こいつは使う度に (評価する度に) ブロック内が評価されるので、

scala> for (x <- List(1,2,3)) println(x+b)
fuga
3
fuga
4
fuga
5
unnamed0: Unit = ()

こんなことになる。キモい。 なんか使い道無いかと考えたけど、思い浮かばなかった。

ちなみに、関数の引数としてブロックを受け取る方法もあって、このページによれば、

scala> def f(x : => int) = x + 1
f: (=> int)Int

scala> f { println("piyo"); 2 }
piyo
unnamed2: Int = 3

こんな感じのようだ。 うーん、何かできそうだよなー。

scala> def myIf[T](bl: =>Boolean)(th: =>T)(el: =>T) = {
     |   bl match {
     |     case true => th
     |     case false => el
     |   }
     | }
myIf: [T >: ? <: ?](=> Boolean)(=> T)(=> T)T

scala> myIf { 1 > 0 } { 'ok } { 'ng }
unnamed4: Symbol = 'ok

うひ、ちょっとおもろい。

お名前:
E-mail:
コメント:

トップ «前の日記(2007-06-21) 最新 次の日記(2007-06-23)» 編集

日記ってのは本来、自分で読み返すためにあるもんだよなあ……
もしくは有名人になったら死後に本になったりとかか?

RSS はこちら

jijixi at azito.com