今回は「純粋関数型の並列処理」の後半、Nonblockingな実装を取り扱います。
ちなみに本書では、Nonblockingの実装は飛ばしても良い、と書かれていますが、後半の章でBlocking実装のPar[A]
ではなく、Nonblocking実装のNonblocking.Par.[A]
が使われますので、実装していきましょう。
純粋関数型の並列処理
Blockingと、Nonblockingについて
本書の解説だけだと少し理解しづらいところがありますが、以下のようなテストコードで違いを確かめられます。skipをコメントアウトして挙動の違いを試してみましょう。
package fpinscala.parallelism import wvlet.airspec.* import java.util.concurrent.* import java.lang.Thread class DeadLockSpec extends AirSpec: skip("通常は実行しない...デッドロックになるから") test("dead lock") { val es = Executors.newFixedThreadPool(2) val p = Par.unit({ (Thread.currentThread.getId, 42) }) val forkerd = Par.fork(Par.fork(Par.fork(p))) info("forkします") val f = forkerd.run(es) info("forkしました") info(f) // ここまではOK...一番外側のforkは動くから...でもその先が動かない val r = f.get r._1 shouldNotBe Thread.currentThread.getId r._2 shouldBe 42 } class NotDeadLockSpec extends AirSpec: test("not dead lock") { val es = Executors.newFixedThreadPool(1) val p = Nonblocking.Par.delay({ (Thread.currentThread.getId, 42) }) val forked = Nonblocking.Par.fork(Nonblocking.Par.fork(Nonblocking.Par.fork(p))) info("forkします") val r = forked.run(es) info("forkしました") info("実行されたスレッドのID:" + r._1) info("メインスレッドのID:" + Thread.currentThread.getId) r._1 shouldNotBe Thread.currentThread.getId r._2 shouldBe 42 }
Actorについて
第1版のActorの実装は十分に短い実装でしたが、第2版でよりシンプルなActorの実装に差し替えられました。こちらの方が理解しやすいですね。
fpinscala/Actor.scala at second-edition · fpinscala/fpinscala · GitHub
infix記法について
infix記法を実現するためにimplicit conversion
が使われていましたが、前回紹介したopaque type
とextension
の組み合わせで実現できます。