- Scala 2.13.0 の REPL
- ScalaCheck 1.14.0
- 限定継続...
- エラーハンドリングの実用例みたいなもの
(敬称略)
-
shift/reset プログラミング入門 - 浅井 健一
-
モナドから始めない継続入門 - @cloudear8
-
Loan パターンのネストは継続モナドでシュッと解決できるよという話 - @jwhaco
-
scalaz.IndexedContsT
(敬称略)
shift/reset プログラミング入門 - 浅井 健一
モナドから始めない継続入門 - @cloudear8
Loan パターンのネストは継続モナドでシュッと解決できるよという話 - @jwhaco
scalaz.IndexedContsT
| def isEven(i: Int): Boolean = 1 % 2 == 0 | |
| val result = isEven(42) | |
| if (result) { | |
| println(s"$n is even number") | |
| } else { | |
| println(s"$n is odd number") | |
| } | |
| // === | |
| val result = isEven(42) | |
| if (result) { | |
| println(s"$n is even number") // 実行される | |
| } else { | |
| println(s"$n is odd number") // 実行されない(捨てられる) | |
| } |
| // === | |
| def add(i: Int, j: Int): Int = i + j | |
| def mul(i: Int, j: Int): Int = i * j | |
| def show(i: Int): String = "num: " + i.toString | |
| val a = add(1, 2) | |
| val b = add(a, 3) | |
| val s = show(b) | |
| println(s) | |
| // === | |
| def add(i: Int, j: Int): Int = i + j | |
| def mul(i: Int, j: Int): Int = i * j | |
| def show(i: Int): String = "num: " + i.toString | |
| val a = add(1, 2) // 計算結果 | |
| // ↓ その後の計算 | |
| val b = mul(a, 3) | |
| val s = show(b) | |
| println(s) | |
| // === | |
| def add(i: Int, j: Int): Int = i + j | |
| def mul(i: Int, j: Int): Int = i * j | |
| def show(i: Int): String = "num: " + i.toString | |
| val a = add(1, 2) | |
| val b = mul(a, 3) // 計算結果 | |
| // その後の計算 | |
| val s = show(b) | |
| println(s) | |
| // === | |
| def add(i: Int, j: Int): Int = i + j | |
| def mul(i: Int, j: Int): Int = i * j | |
| def show(i: Int): String = "num: " + i.toString | |
| val a = add(1, 2) | |
| val b = mul(a, 3) | |
| val s = show(b) // 計算結果 | |
| // その後の計算 | |
| println(s) |
| // 通常の計算(直接スタイル) | |
| def add(i: Int, j: Int): Int = i + j | |
| // 継続渡しスタイル | |
| def add[R](i: Int, j: Int)(cont: Int => R): R = { | |
| val a = i + j | |
| cont(a) | |
| } | |
| // 通常の計算(直接スタイル) | |
| def show(i: Int): String = "num: " + i.toString | |
| // 継続渡しスタイル | |
| def show[R](i: Int)(cont: String => R): R = { | |
| val a = "num: " + i.toString | |
| cont(a) | |
| } | |
| // === | |
| scala> def add[R](i: Int, j: Int)(cont: Int => R): R = { | |
| | val a = i + j | |
| | cont(a) | |
| | } | |
| add: [R](i: Int, j: Int)(cont: Int => R)R | |
| // 1 + 2 を実行。その後の計算で「10倍する」 | |
| scala> add(1, 2)(a => a * 10) | |
| res0: Int = 30 | |
| // 1 + 2 を実行。その後の計算で「引数を返す」 | |
| scala> add(1, 2)(a => a) | |
| res1: Int = 3 | |
| // === | |
| scala> def show[R](i: Int)(cont: String => R): R = { | |
| | val a = "num: " + i.toString | |
| | cont(a) | |
| | } | |
| show: [R](i: Int)(cont: String => R)R | |
| // 1 を文字列に変換し prefix をつける。その後の計算「suffix をつける」 | |
| scala> show(1)(a => a + " !!") | |
| res3: String = num: 1 !! | |
| // 1 を文字列に変換し prefix をつける。その後の計算「Byte 配列を得る」 | |
| scala> show(1)(a => a.getBytes(java.nio.charset.StandardCharsets.UTF_8)) | |
| res4: Array[Byte] = Array(110, 117, 109, 58, 32, 49) | |
| // === | |
| // 直接スタイル(再掲) | |
| val a = add(1, 2) | |
| val b = mul(a, 3) | |
| val s = show(b) | |
| println(s) | |
| // === | |
| // 継続渡しスタイル | |
| add(1, 2){ a => | |
| mul(a, 3){ b => | |
| show(b){ s => | |
| println(s) | |
| } | |
| } | |
| } | |
| // === | |
| // 継続ってどこだっけ? | |
| def add[R](i: Int, j: Int)(cont: Int => R): R = { | |
| val a = i + j | |
| cont(a) | |
| } | |
| def show[R](i: Int)(cont: String => R): R = { | |
| val a = "num: " + i.toString | |
| cont(a) | |
| } | |
| // === | |
| // メソッド定義を再掲 | |
| def add[R](i: Int, j: Int)(cont: Int => R): R = { ??? } | |
| def show[R](i: Int) (cont: String => R): R = { ??? } | |
| // 継続部分を関数に変更 | |
| def add[R](i: Int, j: Int): (Int => R) => R = { cont => ??? } | |
| def show[R](i: Int) : (String => R) => R = { cont => ??? } | |
| // (A => R) => R という関数に Cont[R, A] と名前をつける | |
| type Cont[R, A] = (A => R) => R | |
| def add[R](i: Int, j: Int): Cont[R, Int] = { cont => ??? } | |
| def show[R](i: Int) : Cont[R, String] = { cont => ??? } | |
| // === | |
| // Scala ではしばしばラップするデータ型を定義 | |
| final case class Cont[R, A](run: (A => R) => R) | |
| // もちろん、データ型 Cont[R, A] を用いて add, show が定義できる | |
| def add[R](i: Int, j: Int): Cont[R, Int] = Cont { cont => ??? } | |
| def show[R](i: Int) : Cont[R, String] = Cont { cont => ??? } |
| // === | |
| // Monad にしたい(for式で合成したい) | |
| object Cont { | |
| def pure[R, A](a: A): Cont[R, A] = | |
| ??? | |
| } | |
| final case class Cont[R, A](run: (A => R) => R) { | |
| def flatMap[B](f: A => Cont[R, B]): Cont[R, B] = | |
| ??? | |
| def map[B](f: A => B): Cont[R, B] = | |
| ??? | |
| } | |
| // === | |
| // pure の実装 | |
| object Cont { | |
| def pure[R, A](a: A): Cont[R, A] = | |
| Cont(ar => ar(a)) // pure の実装 | |
| } | |
| final case class Cont[R, A](run: (A => R) => R) { | |
| def flatMap[B](f: A => Cont[R, B]): Cont[R, B] = | |
| ??? | |
| def map[B](f: A => B): Cont[R, B] = | |
| ??? | |
| } | |
| // === | |
| // flatMap の実装 1 | |
| object Cont { | |
| def pure[R, A](a: A): Cont[R, A] = | |
| Cont(ar => ar(a)) | |
| } | |
| final case class Cont[R, A](run: (A => R) => R) { | |
| def flatMap[B](f: A => Cont[R, B]): Cont[R, B] = | |
| Cont(br => ???) // 計算結果を考える | |
| def map[B](f: A => B): Cont[R, B] = | |
| ??? | |
| } | |
| // === | |
| // flatMap の実装 2 | |
| object Cont { | |
| def pure[R, A](a: A): Cont[R, A] = | |
| Cont(ar => ar(a)) | |
| } | |
| final case class Cont[R, A](run: (A => R) => R) { | |
| def flatMap[B](f: A => Cont[R, B]): Cont[R, B] = | |
| Cont(br => run(a => f(a) /* Cont[R, B] */)) // 自身の継続として、引数の関数fにその結果を与える | |
| def map[B](f: A => B): Cont[R, B] = | |
| ??? | |
| } | |
| // === | |
| // flatMap の実装 3 | |
| object Cont { | |
| def pure[R, A](a: A): Cont[R, A] = | |
| Cont(ar => ar(a)) | |
| } | |
| final case class Cont[R, A](run: (A => R) => R) { | |
| def flatMap[B](f: A => Cont[R, B]): Cont[R, B] = | |
| Cont(br => run(a => f(a).run(br))) // fに結果を与えて得られた継続に外側の継続を渡す | |
| def map[B](f: A => B): Cont[R, B] = | |
| ??? | |
| } | |
| // === | |
| // map はモナドのデフォルト実装 | |
| object Cont { | |
| def pure[R, A](a: A): Cont[R, A] = | |
| Cont(ar => ar(a)) | |
| } | |
| final case class Cont[R, A](run: (A => R) => R) { | |
| def flatMap[B](f: A => Cont[R, B]): Cont[R, B] = | |
| Cont(br => run(a => f(a).run(br))) | |
| def map[B](f: A => B): Cont[R, B] = | |
| flatMap(a => Cont.pure(f(a))) // ひとまずモナドのデフォルト実装 | |
| } |
| // === | |
| sbt.version=1.2.8 | |
| ThisBuild / scalaVersion := "2.13.0" | |
| ThisBuild / version := "0.1.0-SNAPSHOT" | |
| ThisBuild / organization := "com.example" | |
| ThisBuild / organizationName := "example" | |
| lazy val root = (project in file(".")) | |
| .settings( | |
| name := "check_cont_monad_law", | |
| libraryDependencies += "org.scalacheck" %% "scalacheck" % "1.14.0" % Test | |
| ) | |
| // === | |
| package example | |
| import org.scalacheck.{Prop, Properties} | |
| final case class Cont[R, A](run: (A => R) => R) { | |
| def map[B](f: A => B): Cont[R, B] = Cont(br => run(f andThen br)) | |
| def flatMap[B](f: A => Cont[R, B]): Cont[R, B] = | |
| Cont(br => run(f(_).run(br))) | |
| } | |
| object Cont { | |
| def pure[R, A](a: A): Cont[R, A] = Cont(_(a)) | |
| } | |
| class ContMonadSpec extends Properties("Monad[Cont[R, ?]]") { | |
| def inc(i: Int): Cont[Int, Int] = Cont(_(i + 1)) | |
| def add_: Cont[String, String] = Cont(_(s + "!")) | |
| def add_?[R](s: String): Cont[String, String] = Cont(_(s + "?")) | |
| property("rightIdentity") = Prop.forAll { i: Int => | |
| inc(i).flatMap(Cont.pure).run(identity) == inc(i).run(identity) | |
| } | |
| property("leftIdentity") = Prop.forAll { i: Int => | |
| Cont.pure[Int, Int](i).flatMap(inc).run(identity) == inc(i).run(identity) | |
| } | |
| property("associativity") = Prop.forAll { s: String => | |
| Cont.pure(s).flatMap(add_!).flatMap(add_?).run(identity) == | |
| Cont.pure(s).flatMap(a => add_!(a).flatMap(add_?)).run(identity) | |
| } | |
| } | |
| // === | |
| sbt:check_cont_monad_law> test | |
| [info] + Monad[Cont[R, ?]].rightIdentity: OK, passed 100 tests. | |
| [info] + Monad[Cont[R, ?]].leftIdentity: OK, passed 100 tests. | |
| [info] + Monad[Cont[R, ?]].associativity: OK, passed 100 tests. | |
| [info] Passed: Total 3, Failed 0, Errors 0, Passed 3 | |
| [success] Total time: 1 s, completed 2019/09/15 19:49:36 |
| // === | |
| def add[R](i: Int, j: Int): Cont[R, Int] = Cont(ar => ar(i + j)) | |
| def mul[R](i: Int, j: Int): Cont[R, Int] = Cont(ar => ar(i * j)) | |
| def show[R](i: Int): Cont[R, String] = Cont(ar => ar(s"num: $i")) | |
| def prog[R]: Cont[R, String] = | |
| for { | |
| a <- add(1, 2) | |
| b <- mul(a, 3) | |
| s <- show(b) | |
| } yield { | |
| s.toUpperCase | |
| } | |
| // === | |
| scala> prog.run(s => s.toList) | |
| res18: List[Char] = List(N, U, M, :, , 9) | |
| scala> prog.run(s => s.length) | |
| res19: Int = 6 | |
| scala> prog.run(s => s) | |
| res20: String = NUM: 9 | |
| // === | |
| def prog[R]: Cont[R, String] = | |
| add(1, 2).flatMap { a => | |
| mul(a, 3).flatMap { b => | |
| show(b).map { s => | |
| s.toUpperCase | |
| } | |
| } | |
| } | |
| // fizzbuzz | |
| def fizzCont(i: Int): Cont[String, Int] = Cont { ar => | |
| if (i % 3 == 0) { | |
| "Fizz" // 継続(ar)を実行しないので計算が "Fizz" で終了する | |
| } else { | |
| ar(i) // 継続に i を渡し、残りの処理を実行する | |
| } | |
| } | |
| // === | |
| def fizzCont(i: Int): Cont[String, Int] = Cont(ar => if (i % 3 == 0) "Fizz" else ar(i)) | |
| def buzzCont(i: Int): Cont[String, Int] = Cont(ar => if (i % 5 == 0) "Buzz" else ar(i)) | |
| def fizzBuzzCont(i: Int): Cont[String, Int] = Cont(ar => if (i % 15 == 0) "FizzBuzz" else ar(i)) | |
| def fizzBuzz(i: Int): Cont[String, Int] = | |
| for { | |
| a <- fizzBuzzCont(i) | |
| b <- fizzCont(a) | |
| c <- buzzCont(b) | |
| } yield c | |
| // === | |
| scala> LazyList.from(1).map(fizzBuzz(_).run(_.toString)).take(15).toList | |
| res54: List[String] = List(1, 2, Fizz, 4, Buzz, Fizz, 7, 8, Fizz, Buzz, 11, Fizz, 13, 14, FizzBuzz) | |
| // === | |
| import scala.util.Try | |
| // Some だったら継続に値を渡して実行。None だったら継続を破棄して ifNone の結果を返す | |
| def someValueOr[R, A](fa: Option[A])(ifNone: => R): Cont[R, A] = | |
| Cont(ar => fa.fold(ifNone)(ar)) // Cont(fa.fold(ifNone)) | |
| // Success だったら継続に値を渡して実行。None だったら継続を破棄して ifFailure の結果を返す | |
| def successValueOr[R, A](fa: Try[A])(ifFailure: Throwable => R): Cont[R, A] = | |
| Cont(ar => fa.fold(ifFailure, ar)) // Cont(fa.fold(ifFailure, _)) | |
| // input から指定された key に対応する値を取り出し、数値型に変換する | |
| def parseInt(input: Map[String, String], key: String): Cont[String, Int] = | |
| for { | |
| s <- someValueOr(input.get(key))(s"not found: $key") | |
| i <- successValueOr(Try(s.toInt))(_.toString) | |
| } yield i | |
| // === | |
| // 例えば下記のような input を仮定する | |
| val input = Map("name" -> "aoiroaoino", "age" -> "17") | |
| parseInt(input, "address").run(i => s"result: $i") | |
| parseInt(input, "name").run(i => s"result: $i") | |
| parseInt(input, "age").run(i => s"result: $i") | |
| // === | |
| scala> parseInt(input, "address").run(i => s"result: $i") | |
| res99: String = not found: address | |
| scala> parseInt(input, "name").run(i => s"result: $i") | |
| res100: String = java.lang.NumberFormatException: For input string: "aoiroaoino" | |
| scala> parseInt(input, "age").run(i => s"result: $i") | |
| res101: String = result: 17 |
| // === | |
| def using[A <: AutoCloseable, B](a: => A, n: String)(f: A => B): B = | |
| try f(a) finally { a.close(); println(s"close: $n") } | |
| // Scala 2.13.0 より scala.util.Using が入った | |
| //話を簡略化するため、例外が投げられる resource を使用 | |
| def resource[R, A](resource: R)(body: (R) => A)(implicit releasable: Releasable[R]): A | |
| // 継続モナドでラップ | |
| def resource[R, A](a: => A)(implicit releasable: Releasable[A]): Cont[R, A] = | |
| Cont(ar => Using.resource(a)(ar)) | |
| // === | |
| def lines(reader: BufferedReader): Iterator[String] = | |
| Iterator.unfold(())(_ => Option(reader.readLine()).map(_ -> ())) | |
| // Using.Magaer を使わず、しかも for 式で合成できる | |
| val prog: Cont[List[String], List[String]] = for { | |
| a <- resource(new BufferedReader(new FileReader("/tmp/file1.txt"))) | |
| b <- resource(new BufferedReader(new FileReader("/tmp/file2.txt"))) | |
| c <- resource(new BufferedReader(new FileReader("/tmp/file3.txt"))) | |
| } yield { | |
| (lines(a) ++ lines(b) ++ lines(c)).toList | |
| } | |
| scala> prog.run(identity) | |
| res140: List[String] = List(Hello, 1, Hello, 2, Hello, 3) | |
| // === | |
| // close() 実行時に println する独自 Releasable を定義 | |
| def r(n: String): Using.Releasable[AutoCloseable] = | |
| _.close().tap(_ => println(s"call close() for $n")) | |
| // Releasable を明示的に渡して実行 | |
| val prog: Cont[List[String], List[String]] = for { | |
| a <- resource(new BufferedReader(new FileReader("/tmp/file1.txt")))(r("f1")) | |
| b <- resource(new BufferedReader(new FileReader("/tmp/file2.txt")))(r("f2")) | |
| c <- resource(new BufferedReader(new FileReader("/tmp/file3.txt")))(r("f3")) | |
| } yield { | |
| (lines(a) ++ lines(b) ++ lines(c)).toList | |
| } | |
| scala> prog.run(identity) | |
| call close() for f3 | |
| call close() for f2 | |
| call close() for f1 | |
| res145: List[String] = List(Hello, 1, Hello, 2, Hello, 3) |
| // === | |
| type User = String | |
| type Result = String | |
| def findAll[R](): Cont[Result, List[User]] = Cont { ar => | |
| try { | |
| ar(List("foo", "bar")) // DB への問い合わせは成功したとする | |
| } catch { | |
| case _: Throwable => "query execution error" | |
| } | |
| } | |
| // === | |
| // 全て成功する場合 | |
| val prog: Cont[Result, List[User]] = | |
| for { | |
| users <- findAll() | |
| names <- Cont.pure(users.map(n => s"[$n]")) | |
| } yield names | |
| scala> prog.run(_.mkString(", ")) | |
| res132: Result = [foo], [bar] | |
| // === | |
| // 継続の深いところで例外が投げられた場合 | |
| val prog: Cont[Result, List[User]] = | |
| for { | |
| users <- findAll() | |
| names <- Cont.pure(users.map(n => s"[$n]")) | |
| _ = 1 / 0 // java.lang.ArithmeticException: / by zero | |
| } yield names | |
| scala> prog.run(_.mkString(", ")) | |
| res133: Result = query execution error | |
| // === | |
| // 後続の処理をなんとなく二回実行するやつ | |
| def twice: Cont[Unit, Unit] = Cont { ar => ar(()); ar(()) } | |
| // 絶対に一回しか実行して欲しくない DB への書き込み | |
| def insert(s: String): Cont[Unit, Unit] = Cont { ar => | |
| println("insert data to database") | |
| ar(()) | |
| } | |
| val prog: Cont[Unit, Unit] = | |
| for { | |
| _ <- twice | |
| _ <- insert("some data") | |
| } yield () | |
| // めでたく二回実行されてしまいました | |
| scala> prog.run(identity) | |
| insert data to database | |
| insert data to database |
| // M[_] はモナドの型 | |
| def pure[A](a: A): M[A] | |
| def flatMap[B](f: A => M[B]): M[B] | |
| // 右単位元 | |
| fa.flatMap(a => pure(a)) == fa | |
| // 左単位元 | |
| pure(a).flatMap(f) == f(a) | |
| // 結合律 | |
| fa.flatMap(f).flatMap(g) == fa.flatMap(a => f(a).flatMap(g)) |