Magnolia Tech

いつもコードのことばかり考えている人のために。

『Scala関数型デザイン&プログラミング』の演習問題をScala3で解く その10

blog.magnolia.tech

今回は「純粋関数型の並列処理」の後半、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 typeextensionの組み合わせで実現できます。

blog.magnolia.tech