来世から頑張る!!

技術ブログを目指して

Free Monadについて

新規性の無い話というものはなかなか話す場所も無いもので、 どこにも話す機会が見つからなかったのでブログに書いて放置しようかと。

Free MonadのApplicative合成

Free MonadMonadです。 なのでApplicative合成ができます。

Applicative合成ができても直列実行しかできない処理もあるんですが、 Freeは抽象的な内容なので可能なら並列実行したいわけです。

scalazやcatsなどを調べてみると、Freeとは別にFreeApplicativeと呼ばれるクラスがあります。 これらがApplicativeで並列実行可能なように実装されているだけで、 Free側はApplicative合成しても直列化される実装のよう(?)です。

(catsは2.1.1, scalazは7.3.0を使用しています。)

正直、どちらのFreeもすごく難しそうなチューニングがしてあって、 どこがどうなっているのかすらわかりません。

ということで、最適化とかそういう難しいことは考えずに、 最低限の実装でFree Monad + Free Applicativeを実装してみようという話です。

実装

全体のコードはgithubに置いてあります。

開始点

一般的なFreerを出発点とします。

sealed trait Free[F[_], A] {
  def map[B](f: A => B): Free[F, B] = flatMap(f.andThen(Free.pure))
}

object Free {
  final case class Pure[F[_], A](a: A) extends Free[F, A]
  final case class Impure[I, F[_], A](fi: F[I], f: I => F[A]) extends Free[F, A]

  def pure[F[_], A](a: A): Free[F, A] = Pure(a)
}

合成のクラス化

まずは合成の操作がわかりやすくなるようにKleisli部分を別クラスに切り出します。

- final case class Impure[I, F[_], A](fi: F[I], f: I => F[A]) extends Free[F, A]
+ final case class Impure[I, F[_], A](fi: F[I], arrow: Arrow[I, F, A]) extends Free[F, A]
sealed trait Arrow[A, F[_], B] {
  def run(fa: F[A])(implicit M: Monad[F]): F[B]
}

object Arrow {
  final class Bind[A, F[_], B](f: A => Free[F, B]) extends Arrow[A, F, B] {
    override def run(fa: F[A])(implicit M: Monad[F]): F[B] = M.bind(fa)(f)
  }
  final class Sequence[A, X, F[_], B](a1: Arrow[A, F, X], a2: Arrow[X, F, B]) extends Arrow[A, F, B] {
    override def run(fa: F[A])(implicit M: Monad[F]): F[B] = a2.run(a1.run(fa))
  }
}

Monadはなんでも良いのですが、scalaz.Monadを使用しています。

Applicative用のArrowを導入

F[A => B]を合成できるようにArrowにコンストラクターを追加します。

object Arrow {
  // 略
  final case class Apply[A, F[_], B](f: Free[F, A => B]) extends Arrow[A, F, B] {
    override def run(fa: F[A])(implicit M: Monad[F]): F[B] = f match {
      case Free.Pure(ff) => M.map(fa)(ff)
      case Free.Impure(fi, arrow) => M.ap(fa)(arrow.run(fi))
    }
  }
}

fがFreeなので少々厄介ですが、型合わせをすれば動くかと。

きっとここまででも目的は達成していると思うのですが、 この部分のImpureの場合のarrowがほとんどの場合無駄な気がするので専用の型を用意します。

Arrow.Idenittyの導入

とくに何もしないArrowを用意します。 名称はIdentityが正しいのか、reflectが正しいのかわかりませんが、なんとなく前者で。

object Arrow {
  // 略
  final case class Identity[F[_], A]() extends Arrow[A, F, A] {
    override def run(fa: F[A])(implicit M: Monad[F]): F[A] = fa
  }
}

あとはなんかまあ、いい感じに。

テスト実行

わかりやすそうなオブジェクトとしてmonixのTaskで実行してみます。

Utility methodの準備

簡単にthreadを専有できるThread.sleepの出番ですね。

import monix.eval.Task

def task[A](id: String, ms: Long, value: => A): monix.eval.Task[A] = monix.eval.Task {
  try {
    println(s"id: $id started ${Instant.now()}")
    Thread.sleep(ms)
    value
  } finally {
    println(s"id: $id finished ${Instant.now()}")
  }
}

implicit val taskMonad: scalaz.Monad[Task] = new scalaz.Monad[Task] {
  override def ap[A, B](fa: => Task[A])(f: => Task[A => B]): Task[B] = Task.parMap2(fa, f)((a, ff) => ff(a))
  override def apply2
  override def bind[A, B](fa: Task[A])(f: A => Task[B]): Task[B] = fa.flatMap(f)
  override def point[A](a: => A): Task[A] = Task(a)
}

Monad instance

FreeをscalazのMonadに依存するように作ってしまったので、TaskのMonad instanceを準備します。 ついでにFree自身のinstanceも作ってしまいます。

  implicit val taskMonad: scalaz.Monad[Task] = new scalaz.Monad[Task] {
  override def ap[A, B](fa: => Task[A])(f: => Task[A => B]): Task[B] = Task.parMap2(fa, f)((a, ff) => ff(a))
  override def apply2[A, B, C](fa: => Task[A], fb: => Task[B])(f: (A, B) => C): Task[C] = Task.parMap2(fa, fb)(f)
  override def bind[A, B](fa: Task[A])(f: A => Task[B]): Task[B] = fa.flatMap(f)
  override def point[A](a: => A): Task[A] = Task(a)
}

implicit def freeMonad[F[_]: scalaz.Monad]: scalaz.Monad[yafmi.Free[F, *]] = new Monad[yafmi.Free[F, *]] {
  override def ap[A, B](fa: => Free[F, A])(f: => Free[F, A => B]): Free[F, B] = fa.ap(f)
  override def apply2[A, B, C](fa: => Free[F, A], fb: => Free[F, B])(f: (A, B) => C): Free[F, C] =
    fa.map2(fb)(f)
  override def bind[A, B](fa: Free[F, A])(f: A => Free[F, B]): Free[F, B] = fa.flatMap(f)
  override def point[A](a: => A): Free[F, A] = Free.pure(a)
}

実行

簡単なサンプルを実行してみます。

import scala.concurrent.duration.DurationInt
import monix.execution.Scheduler.Implicits.global 

val x = {
  import yafmi.Free
  val a = Free.liftF(task("a", 4000, 42)).flatMap(i => Free.liftF(task("b", 1000, i * 2)))
  val b = Free.liftF(task("c", 3000, 3.14)).flatMap(d => Free.liftF(task("d", 2000, d * 10)))
  val f1: (Int, Double) => Long = (i, d) => (i * d).toLong
  a.map2(b)(f1)
}

x.run.runSyncUnsafe(30.seconds)
id: c started 2020-05-04T12:56:52.614052Z
id: a started 2020-05-04T12:56:52.615491Z
id: c finished 2020-05-04T12:56:55.618629Z
id: d started 2020-05-04T12:56:55.620649Z
id: a finished 2020-05-04T12:56:56.619649Z
id: b started 2020-05-04T12:56:56.620590Z
id: b finished 2020-05-04T12:56:57.625377Z
id: d finished 2020-05-04T12:56:57.625387Z

val res0: Long = 2637

いい感じですね。 ついでにApplicativeBuilderも試しておきましょう。

val y = {
  import yafmi.Free
  import scalaz.syntax.applicative.ToApplyOpsUnapply
  val a = Free.liftF(task("a", 4000, 42)).flatMap(i => Free.liftF(task("b", 1000, i * 2)))
  val b = Free.liftF(task("c", 3000, 3.14)).flatMap(d => Free.liftF(task("d", 2000, d * 10)))
  val f1: (Int, Double) => Long = (i, d) => (i * d).toLong
  (a |@| b)(f1)
}

y.run.runSyncUnsafe(30.seconds)
id: c started 2020-05-04T12:59:04.214376Z
id: a started 2020-05-04T12:59:04.214464Z
id: c finished 2020-05-04T12:59:07.216922Z
id: d started 2020-05-04T12:59:07.217325Z
id: a finished 2020-05-04T12:59:08.219095Z
id: b started 2020-05-04T12:59:08.219483Z
id: d finished 2020-05-04T12:59:09.220411Z
id: b finished 2020-05-04T12:59:09.222420Z

val res1: Long = 2637

次回へ

他の型から変換する例などでも意図通りに動くのか確かめてみたいですね。 今日はここまで。

FreeのRunnerを抽象化する

モチベーション

差し替え時の変更忘れなどを防ぐために、Runner部分を抽象化してConstructor Injectionしたい。

Free

ここは今回重要ではないので、Functorの要らない単純な実装を用意。

sealed trait Free[F[_], A] {
  def map[B](f: A => B): Free[F, B] = flatMap(a => Free.pure(f(a))
  def flatMap[B](f: A => Free[F, B]): Free[F, B] = this match {
    case Free.Pure(a) => f(a)
    case Free.Impure(fi, g) => Free.impure(fi, g.andThen(_.flatMap(f)))
  }
}

object Free {
  final case class Pure[F[_], A](a: A) extends Free[F, A]
  final case class Impure[F[_], I, A](fi: F[I], f: I => F[A]): extends Free[F, A]

  def pure[F[_], A](a: A): Free[F, A] = Pure(a)
  def impure[F[_], I, A](fi: F[I], f: I => F[A]): Free[F, A] = Impure(fi, f)
  def liftF[F[_], A])(fa: F[A]): Free[F, A] = impure(fa, pure)
}

Reader

簡単な何か。

object Reader {
  sealed abstract case class Ask[I, A](run: I => A)

  def ask[I](i: I): Free[Ask[I, ?], I] =
    Free.liftF(new Ask(identity[I]) { })
}

ビジネスロジック

何か適当に。

def powers(times: Int): Free[Reader.Ask[Int, ?], List[Int]] = {
  def pow(i: Int, j: Int, acc: Int = 1): Int = {
    if (j <= 0) acc
    else pow(i, j - 1, acc * i)
  }

  def loop(i: Int, acc: Free[Reader.Ask[Int, ?], List[Int]]): Free[Reader.Ask[Int, ?], List[Int]] = {
    if (times < i) acc.map(_.reverse)
    else {
      val next = for {
        list <- acc
        j <- Reader.ask[Int]
      } yield pow(j, i) +: list
      loop(i + 1, next)
    }
  }

  loop(1, Free.pure(List.empty))
}

Runner

抽象化していない実装。

object ReaderRunner {
  @tailrec
  def run[I, A](fa: Free[Reader.Ask[I, ?], A])(i: I): A = fa match {
    case Free.Pure(a) => a
    case Free.Impure(ask, f) => run(f(ask.run(i)))(i)
  }
}
ReaderRunner.run(powers(3))(2) // => List(2, 4, 8)

Runnerを抽象化する。

変更したい部分は入力と戻り値の2か所。
型パラメーターにこの部分を取れば、抽象化できる。

trait ReaderRunner[I[_], O[_]] {
  def run[P, A](fa: Free[Reader.Ask[P, ?], A])(p: I[P]): O[A]
}

実装を複数定義可能に。

object ReaderRunner {
  type Id[A] = A
  object Simple extends ReaderRunner[Id, Id] {
    @tailrec
    def run[P, A](fa: Free[Reader.Ask[P, ?], A])(p: P): A = fa match {
      case Free.Pure(a) => a
      case Free.Impure(ask, f) => run(f(ask.run(p)))(p)
    }
  }

  object ListInput extends ReaderRunner[List, Option] {
    @tailrec
    def run[P, A](fa: Free[Reader.Ask[P, ?], A])(p: List[P]): Option[A] =
      fa match {
        case Free.Pure(a) => Some(a)
        case Free.Impure(ask, f) =>
          if (p.length == 0) None
          else run(f(ask.run(p.head)))(p.tail)
      }
  }
}
ReaderRunner.Simple.run(powers(3))(2)
// => List(2, 4, 8)
ReaderRunner.ListInput.run(powers(3))(List(1, 2, 3, 4, 5))
// => Some(List(1, 4, 27))

おまけ

({type L[A] = String})#Lみたいに定義すると、元の戻り値を無視することが可能。 State[S, A]({type L[A] = (S, A)})#LSのみ返す実装にするときなどに使える。

enrich my library

.runみたいにしたい場合、rmlでなんとかできる。

implicit class RunReader[P, A](val fa: Free[Reader.Ask[P, ?], A]) extends AnyVal {
  def runReader[I[_], O[_]](p: I[P])(implicit RR: ReaderRunner[I, O]): O[A] = RR.run(fa)(p)
}
implicit val rr: ReaderRunner[List, Option] = ReaderRunner.ListInput
powers(11).runReader(List(1, 3, 5))

さらなる抽象化?

Free[F[_], A]Fの部分を型パラメーターに取れば、FreeRunnerのようなものも作成はできる。 ただ、引数の数などを固定することにあまり旨味が見えないので、過度な抽象化のように思う。

なお、EffのRunnerでも戻り値の型が変わるぐらいで同様の事が可能。

Mac GerdtsのIMPERIAL(インペリアル)の日本語訳で気になったところ

PD VerlagのIMPERIAL, ニューゲームズオーダーの日本語訳付きを買いました。

日本語訳ルールの他国に行軍ときの動きの訳が自分の解釈と違うので、メモ。

日本語訳

友好的な態度を取らせる場合は、駒を寝かせます。この友好的な陸軍駒は、州の活動を妨げません。この態度は任意のタイミング で変更できます。

他国を進行する陸軍コマは態度を「友好的」か「敵対的」かで選択できるのですが、日本語訳では「任意のタイミングで変更できる」となっています。

任意というのはその国の人が陸軍を生産する直前に変更できるのでしょうか。

ほかの言語では?

せっかく元の説明書が入っているので、どんな感じなのか確認してみます。

英語

During the next maneuver turn of the nation, the status of the armies can be changed, even without moving them into another province.

その国の次の行軍時、陸軍の状態は変更可能です。その際、必ずしも別区域に移動する必要はありません。

ドイツ語

Erst bei dem nächsten Manöverzug des Landes kann der Status der Armeen geändert werden (auch ohne dass diese in eine andere Provinz gezogen werden).

次に行軍したときにのみ、陸軍の状態は変更可能です(移動せずに行うことも可能です)。
Erstが英語のOnlyに当たる単語でdem nächstenが次のなので、いつでもは動かせなさそうです。

結論

陸軍コマの状態を変更できるのは、行軍したときのみのようです。

こういう時、元のルールが入っているのはすごく幸せですよね。

※2019-5-3追記:
サイトに公開されている古い方の訳ではこの国家が次に「行軍」を行う際は、となっていることを確認しました。

IndexedContを使ってみよう: 継続編

この記事は?

IndexedCont便利なのでみんな使ってほしいとの思いの元、前提となる知識から解説する記事です。

前回はfor式を使ったエラーハンドリングの基本的なことを書きました。

なお、今回もIndexedCont出てきません

継続

今回も引き続きforを使ってエラーハンドリングしていきます。

相変わらずソースコードはメモにスマートフォンで直書きなので、コンパイルしていません。
きっとそのうち修正されます。

Cont データ型

今回はContという型を使ってエラーを処理していきます。

case class Cont[R, A](run: (A => R) => R)

object Cont {
  def point[R, A](a: A): Cont[R, A] = Cont(f => f(a))
}

あまり型パラメーターや関数型プログラミングになじみのない方には謎な表現だと思われますが、ぜひ使ってほしいので丁寧に説明します。

覚えておいてほしいのは、ほとんどの場合構造を頭で理解するよりも使って慣れていく方が簡単だということです。

Contとは

たぶんContinuation(継続)の略です。

唯一の変数であるrunの型は「[(A => R)という関数を受け取ってRを返す]関数」です。

  • R: 目指す結果(Result)
  • A: 現在の値
  • A => R: 現在の値から結果までの続きの処理(継続)
scala> val a = Cont.point[String, Int](42)
a: Cont[String, Int] = Cont(<function>)

scala> val b = a.run(i => i.toString)
b: String = 42

map

Contmapを定義します。

case class Cont[R, A](run: (A => R) => R) {
  def map[B](f: A => B): Cont[R, B] = Cont(g => run(f andThen g))
}

AからR向かう処理の途中でf: A => BというAからBまでの処理を受け取ったので、残りはB => Rまでとなるのがmapです。

scala> val a = Cont.point[String, Int](65)
a: Cont[String, Int] = Cont(<function>)

scala> val b = a.map(_.toChar) // 現在の値はCharの 'A' (たぶん)
b: Cont[String, Char] = Cont(<function>)

scala> val c = b.map(c => List(c, c, c)) // 現在の値はList('A', 'A', 'A')
c: Cont[String, List[Char]] = Cont(<function>)

scala> val d = c.run(_.mkString("-"))
d: String = A-A-A

普通のプログラミングの1行ずつがすごく難しそうになっただけですね。

エラー処理(継続の破棄)

Contの何が便利かというと、runの引数で渡ってきたA => Rは必ずしも使わなくても良いということです。

使い道は無いもののわかりやすい例として、数値の処理はするけれども100以上は扱わない関数を作ってみます。

def smallOnly(i: Int): Cont[String, Int] = Cont { f =>
  if (i < 100) {
    f(i)
  } else {
    "大きすぎてムリです。"
  }
}

iが100以下ならば続きの処理をiに対して実行するけれど、それ以外の場合は既にあきらめるContを作れます。

scala> val a = smallOnly(65)
a: Cont[String, Int] = Cont(<function>)

scala> val b = a.map(_.toChar) // 現在の値はCharの 'A' (たぶん)
b: Cont[String, Char] = Cont(<function>)

scala> val c = b.map(c => List(c, c, c)) // 現在の値はList('A', 'A', 'A')
c: Cont[String, List[Char]] = Cont(<function>)

scala> val d = c.run(_.mkString("-"))
d: String = A-A-A

scala> val o = smallOnly(101) // iが100以上なので Cont(_ => "大きすぎてムリです。")
o: Cont[String, Int] = Cont(<function>)

scala> val p = a.map(_.toChar) // Cont(f => a.run(i => f(i.toChar))) だけど、a.runには何を渡しても無視される。
p: Cont[String, Char] = Cont(<function>)

scala> val q = b.map(c => List(c, c, c)) // 同上
q: Cont[String, List[Char]] = Cont(<function>)

scala> val r = c.run(_.mkString("-"))
r: String = 大きすぎてムリです。

なんとなくエラー処理に使えそうではないでしょうか。

flatMap

for式と言えばflatMap(詳しくはWebで)なので、定義していきましょう。

case class Cont[R, A](run: (A => R) => R) {
  // map省略
  def flatMap[B](f: A => Cont[R, B]): Cont[R, B] = Cont(g =>run(a => f(a).run(g)))
}

これでforの中でContが使えるようになります。

for {
  a <- Cont.point[String, Char]('a')
  b <- Cont.point[String, Int](3)
} yield (a.toInt + b).toChar
// => Cont[String, Char](f => f('d'))

Contによるエラー処理

前回の記事で定義したgetAなどを使って、 Eitherのように失敗した位置のわかる合成をしてみましょう。

注意点としては、Either[E, A]がエラー時の型Eと成功時の型Aを返してきていたのに対し、 Cont[R, A]では最終的にRの型一つしか返せないということです。

そのため、結果の型を以下のように定義します。

sealed trait Result

// 失敗系
case class ANotFound(id: AID) extends Result
case class BNotFound(id: BID) extends Result
case class CNotFound(id: CID) extends Result

// 成功
case class Succeed(a: A, b: B, c: C) extends Result

成功した場合も失敗した場合もResultということになります。
先ほどStringで"大きすぎて・・・"と失敗を表現していたのと同じですね。

def resolveA(id: AID): Cont[Result, A] = Cont { f =>
  val a: Option[A] = getA(id)
  val b: Option[Result] = a.map(f) // getAがSomeならば継続`f`を適用
  val c: Result =b.getOrElse(ANotFound(id)) // Noneならば`f`は無視してANotFound
  c // 一行書くならで getA(id).fold(ANotFound(id))(f)
}

def resolveB(id: BID): Cont[Result, B] = Cont(f =>getB(id).fold(BNotFound(id))(f))
def resolveC(id: CID): Cont[Result, C] = Cont(f =>getC(id).fold(CNotFound(id))(f))

def resolve(aID: AID): Cont[Result, Succeed] = for {
  a <- resolveA(aID)
  b <- resolveB(a.bID)
  c <- resolveC(b.cID)
} yield Succeed(a, b, c)

実行は以下のような感じです。

scala> val a = resolve(AID(5)).run(x => x)
a: Reslut = ANotFound(AID(5))

scala> val b = resolve(AID(6)).run(x => x)
b: Reslut = BNotFound(BID(12))

scala> val c = resolve(AID(19)).run(x => x)
c: Reslut = CNotFound(CID(11))

scala> val c = resolve(AID(5)).run(x => x)
c: Reslut = Succeed((A(AID(8), BID(16)), B(BID(16), CID(8)), C(CID(8)))

EitherContの使い分け

ここまで見ると中身は大きく違うものの、EitherContでは大体同じような用途に使えそうなことがわかります。
どちらもforの上から順に処理を実行していき、どこかで失敗すると続きは実行されずにエラーケースになります。
では、どのように使い分けるのでしょうか。

一番重要なポイントは戻ってくる値の型です。 Either[E, A]では処理がすべて終了した後でもEAの二つの型が返ってきます。

それに対してCont[R, A]では、値を取り出すためにrunする必要があり、 runした結果返ってくるのはR型ただ一つだけです。

これはつまりEitherが失敗した場合に失敗したことを関数の呼び出し側に伝え、エラー処理の分岐を任せているのに対し、 Contではエラー処理自体も関数の中で完結していることを表します。

Contを返すということは「エラー処理はやり終えた」ということを伝える意思表示になっています。

もちろんmatchや正規表現などを使ってRの中身を調べての分岐は不可能ではありませんが、やるべきではないでしょう。

よって、使い分けの目安としては以下のようになります。 - Either: 中間処理など、エラーは伝えたいけれどどう処理するかは決めたくない部分 - Cont: 外部入出力など、エラーの場合でも何か正常な処理を返さないといけない部分

次回への継続

次回はいよいよ本題であるIndexedContを扱いたいと思います。

IndexedContを使ってみよう: 前提知識編

この記事は?

IndexedCont便利なのでみんな使ってほしいとの思いの元、前提となる知識から解説する記事です。 なお、次の次の回ぐらいまでIndexedCont出てきません

本編

この記事ではエラーハンドリングのためにIndexedContを使えるようになることを目標とします。

Monad Transformerには触れませんので、別記事を探してください。

Step 1: Optionによるエラーハンドリング

理解しやすいエラーハンドリングの基本として、Optionを使用したエラーハンドリングの例からスタートします。

対象コード

必要十分な最低限の例として、下記のようなオブジェクトを取り扱います。

case class AID(value: Int)
case class BID(value: Int)
case class CID(value: Int)

case class A(id: AID, bID: BID)
case class B(id: BID, cID: CID)
case class C(id: CID)

case clas Found(a: A, b: B, c: C)

// 取得用のインターフェース(traitなどに定義): 見つからない場合はNoneを返す
def getA(id: AID): Option[A]
def getB(id: BID): Option[B]
def getC(id: CID): Option[C]

def find(aID: AID): Option[Found]

findを実装するのが目的となります。

実装

取得用インターフェースはわかりやすい例として下記のように実装してみます。

def getA(id: AID): Option[A] =
  if (id.value % 2 == 0) Some(A(id, BID(id.value * 2)))
  else None

def getB(id: BID): Option[B] =
  if (id.value > 12) Some(B(id, CID(id.value - 8)))
  else None

def getC(id: CID): Option[C] =
  if (id.value < 10) Some(C(id))
  else None

これに対して、findの実装は以下のようになります。

def find(aID: AID): Option[Found] = for {
  a <- getA(aID)
  b <- getB(a.bID)
  c <- getC(b.cID)
} yield Found(a, b, c)

解説

Optionのfor式はどこかでNoneが返ってきたらその行以降は実行されずにNoneが返ります。

scala> find(AID(5)) // Aが見つからない
res0: Opiton[Found] = None

scala> find(AID(6)) // Bが見つからない
res1: Opiton[Found] = None

scala> find(AID(18)) // Cが見つからない
res2: Opiton[Found] = None

scala> find(AID(8)) // すべて見つかる
res3: Opiton[Found] = Some(Found(A(AID(8), BID(16)), B(BID(16), CID(8)), C(CID(8))))

Step 2: Eitherを使用してエラー内容を特定する

Optionではどこか途中で失敗すると、どこで失敗してもNoneとなります。

どこで失敗したかを知りたい場合の一番シンプルな方法がEitherです。

対象コード

Optionの例そのままで、findだけ変更します。

def find(aID: AID): Either[String, Found]

実装

こちらもfind以外はそのままです。

def find(aID: AID): Either[String, Found] = for {
  a <- getA(aID).toRight(s"AID: ${aID}のAが見つかりませんでした。")
  b <- getB(a.bID).toRight(s"BID: ${a.bID}のBが見つかりませんでした。")
  c <- getC(b.cID).toRight(s"CID: ${b.cID}のCが見つかりませんでした。")
} yield Found(a, b, c)

解説

EitherOptionと同様に1度でもLeftになるとそれ以降の行は実行されません。

scala> find(AID(5)) // Aが見つからない
res0: Either[String, Found] = Left("AID: 5のAが見つかりませんでした。")

scala> find(AID(6)) // Bが見つからない
res1: Either[String, Found] = Left("BID: 12のBが見つかりませんでした。")

scalva> find(AID(19)) // Cが見つからない
res2: Either[String, Found] = Left("CID: 11のCが見つかりませんでした。")

scala> find(AID(8)) // すべて見つかる
res3: Either[String, Found] = Right(Found(A(AID(8), BID(16)), B(BID(16), CID(8)), C(CID(8))))

ただし、ひとつのfor式の中で使われるEitherはすべて左側の型が同じである必要があります。

応用

左側がStringでは扱いにくいので、自身で定義した型をここに入れたりできます。

sealed trait FindingError

case class ANotFound(id: AID) extends FindingError
case class BNotFound(id: BID) extends FindingError
case class CNotFound(id: CID) extends FindingError

def find(aID: AID): Either[FindingError, Found] = for {
  a <- getA(aID).toRight[FindingError](ANotFound(aID))
  b <- getB(a.bID).toRight(BNotFound(a.bID))
  c <- getC(b.cID).toRight(CNotFound(b.cID))
} yield Found(a, b, c)

次回にむけて

次回はIndexedContのシンプルな形Contを扱います。

たぶん今回の記事にも時間があれば追記します。 (コードを実際に動かす時間がなかったので動かしてみて修正したりします。)

ボードゲームのハコについて

このエントリーはボドゲ紹介 Advent Calendar 2017の7日目です。

前日: 推理ゲーム『ワトソン&ホームズ』 - kano-e no memo
翌日: テラフォーミング・マーズの魅力: うぃりあむの「ヴァリアントの森」

タイトルの通り、ボドゲのハコを紹介したいと思います。

先日ゲームマーケット2017が実施されましたが、そのなかでとある出展者さんが「ダイスタワーという言葉を知らない人が検索フォームからダイスタワーに出会う可能性は限りなく低い」というようなお話をしており、私も知らなければ出会う可能性がすごく低いものを紹介しようとこのエントリーにしました。

ここではBox insertと呼ばれるアクセサリーを紹介致します。

Box insertとは

ボードゲームの箱って内容物に対して小さすぎて、中身を小分けするとフタが閉まらなかったりしますよね?
拡張も一緒に持ち運びたいのに、スペースが足りずに諦めたりしますよね?

f:id:kazzna:20171207221826j:plain
ボードゲームコンポーネントと入りきらない拡張

空気が主なコンポーネント? そういうボードゲームの事は一旦忘れましょう。

こういった大量のコンポーネントをうまく詰め込めるように作られた箱がbox insertです。

Box insertを使うと、先程のようなコンポーネントがうまく箱に収まります。 f:id:kazzna:20171207222041j:plain f:id:kazzna:20171209001801j:plain

f:id:kazzna:20171207222116j:plain
拡張とともに1箱へ
通常はスペースなく箱の中身がきっちり埋まるように作られているので、蓋が開かないように留めてしまえば縦置きも可能です。

注文と組み立て

自分で組み立てする形態で売られていることがほとんどで、注文すると数週間程度で下の写真のような板が届きます。 f:id:kazzna:20171207222315j:plain

組み立てには以下のものが必要になるので100均で揃えてください。

  • 木工用ボンド
  • 紙ヤスリ(120番と400番あたりがあると大丈夫です。)
  • 軍手
  • 綿棒(大量にあるといい感じ)
  • ゴムハンマー(なくても大丈夫かも)

基本的にはプラモデルと同じで、枠から取り外して説明書の通りにくっつけるだけです。
ただ、木製なので細いパーツが折れやすかったり、トゲが手に刺さったりするのでそこは注意が必要です。 万が一パーツが折れた場合は、要らないフレーム部分をヤスリで削って出てきた粉とボンドを混ぜてくっつけることが可能です。

1時間ぐらいで完成すると書かれていますが、そこはボードゲームと同じで大体半日はかかります。
慣れた人が回せば早いんでしょうね。

ニスとかはよく分からないので詳しい人教えてください。

販売サイト紹介

box insertは大きな会社から個人の副業っぽいところまで、多数のサイトで販売されています。
色々と探し回ってあっちがいいこっちがいいと比べて回るのが楽しさの醍醐味です!!!

個人的に気になるところをいくつかピックアップしてご紹介致します。

Meeple Realty

Think inside the box(箱の中について考える)がテーマのサイトです。
ただの木箱ではなくて、ゲームのテーマに合った形に加工されているのが特徴です。 その分使い勝手は他に少し劣ることもありますが、インサートなしよりは確実に使いやすいのでデザインを重視する人にオススメです。

Daedalus Productions

冒頭のテラミスティカのインサートを販売しているところです。
Quick-Start Insertという取り出してすぐにプレイできるようにと作られたシリーズの使い勝手が素晴らしい出来です。

組み立ての説明書がサイト上で公開されていますので、イメージをつかみたい場合にどうぞ。

The Broken Token

インサートだけでなく、オーガナイザー(organizer)やコインなども販売しているサイトです。
プラスチック性の蓋などの木製だけじゃないコンポーネントがついていたり、カードのケースがそのままカードフィーダーになったりと色々素敵なものが沢山あります。

最近追加されたテラミスティカのインサートの出来も良さそうなので、もし既に持っていなければここのを買っていたと思います。

Etsy

ハンドメイド作品を気軽に販売できるサイトです。
アクセサリーや服から大型家具まで幅広く売られており、ゲーム関連アクセサリーも大量にあります。

販売者は個人の場合も多数あるようで、普通のお店の感覚では挑まない方が無難かもしれません。
コンポーネントの質はきっとピンきりなんでしょうけど、お値段の割に非常に良いものも多いので探す価値は大です。

システム的にはカートに入れるボタンを押して支払いを済ませれば買えるのですが、他の海外ショップでお問い合わせぐらいはできるようになってからの利用をオススメします。

Insert here

フォームコアによるインサートを作っているサイトです。 家族でやっている?らしいです。
フォームコアはプラスチックっぽい謎の物体で、ネットで調べる限りでは木の箱よりも軽そうです。

木製のインサートはそれなりの重量になるので、軽いならいつかチャレンジしてみたいと思っています。

その他の気になるサイト

インサートじゃなかったりもしますが、気になるサイトをいくつか。

選び方のポイント

正直、箱に何を求めるかなんて人それぞれだと思いますが、私の経験と好みからの選ぶ基準を記載しておきます。

カードがスリーブ付きで入るのかどうか

トークンの多いゲームでは特に、スペースの都合でカードはスリーブ無しでしか格納できないものもあります。
別のサイトで売られているものはスリーブ付きでも入るということもままあるので、スリーブの有無は確認しましょう。

蓋は閉まらなくてもそこまで困らない

インサートによっては箱の上端より数センチ上まで飛び出ているものもありますが、蓋が一番下までいかなくても意外に困らないものです。
見た目にこだわる場合は避けるべきでしょうし一概には言えませんが、うまくバランスの取れているものを選びたいものです。

みんなに配る駒は別々のトレイに

それぞれのプレイヤー用トークンや開始時の手札など、配ってしまうものはそれぞれ別のトレイに入っていてそのまま配れるものの方が便利です。
また、コインなどの共通のストックに置いておくトレイが2つに分かれていると、それぞれをボードの左右に置いたりなど便利さが上がるのでチェックしておくとよいと思います。

何が格納できるのか

拡張なども一緒に格納できるのかどうかの確認は重要です。
拡張を持っていない場合拡張入れは空っぽになるものの、そのトレイを入れないと中で崩れるので空のまま入れておかなければ傾けられません。
中には「プロモボードがないと隙間ができるから縦向けにはできないよ」というものもあるので注意してください。

お気に入りの箱を探す際の参考程度にどうぞ。

以上、良いボードゲームライフをお過ごしください。

Scalaでの🍣の数え方

最近のおっさんはビールもきちんと数えられないことに驚愕したので、ScalaですがJavaの話をします。 昔と違ってUTF-8の半角カナが3バイトだと信じてくれないおじさんとは出会わなくなってきたなと思ってたのに、世の中そう甘くはなかったようです。

Javaの話なのでClojureだろうがKotlinだろうがJasminだろうが基本的におんなじです。

文字の数え方

🍺🍺🍺 <- これ、いくつかと聞かれたら3と答えて欲しいわけですよ!

scala> val beers = "🍺🍺🍺"
beers: String = 🍺🍺🍺

scala> print(beers.length)
6

当然、emojiなのでサロゲートペアですよ。 codePointCountを使ってくださいねという話です。

scala> print(beers.codePointCount(0, beers.length))
3

せっかくなのでScalaらしくpimpしちゃいましょう。 メソッド名はStringOpsとかとかぶらないように・・・

implicit class StringCount(val s: String) extends AnyVal {
  def letterCount: Int = s.codePointCount(0, s.length)
}

これでbeers.letterCountで文字数が出せるわけですね!

合成文字にも対応しておく

せっかくなので合成文字の数え方にも対応しておきましょう。
参考: Java SE 6 バラバラにして組み立てて - Normalizer

Java(というかUnicode)の正規化には2種類(2段階)あって、Å("\u0041\u030a")のような合成文字をÅ("\u00c5")に圧縮するだけの正規化Canonical Composeと、同じ意味を表すもっと(だいたい英語圏民に)一般的そうな文字に置き換えるCompatibility Composeがあります。

前者に使うのがjava.text.Normalizer.Form.NFC、後者に使うのがjava.text.Normalizer.Form.NFKCです。

試してみましょう。

scala> val a = "\u0041\u030a"
a: String = Å

scala> val b = "\u00c5"
b: String = Å

scala> a == b
res0: Boolean = false

scala> val c = Normalizer.normalize(a, Normalizer.Form.NFC)
c: String = Å

scala> b == c
res1: Boolean = true

scala> val d = "㌔㍉"
d: String = ㌔㍉

scala> val e = Normalizer.normalize(d, Normalizer.Form.NFC)
e: String = ㌔㍉

scala> val f = Normalizer.normalize(d, Normalizer.Form.NFKC)
f: String = キロミリ

文字数を数えたいので、使うのはNFCですね。

import java.text.Normalizer
implicit class StringCount(val s: String) extends AnyVal {
  def normalized: String = Normalizer.normalize(s, Normalizer.Form.NFC)
  def letterCount: Int = {
    val n = this.normlized
    n.codePointCount(0, n.length)
  }
}
scala> val x = "🍺\u0041\u030a🍣\u0041\u030a🍺"
x: String = 🍺Å🍣Å🍺

scala> x.length
res0: Int = 10

scala> x.letterCount
res1: Int = 5

寿司が2貫で1個なのかはともかく、酔っぱらっても1つのビールが2つに見えるようにはなりたくないものです。