Skip to content

Instantly share code, notes, and snippets.

@aoiroaoino
Last active September 16, 2019 06:28
Show Gist options
  • Select an option

  • Save aoiroaoino/f017458c29d0b98eeaf239529f04af22 to your computer and use it in GitHub Desktop.

Select an option

Save aoiroaoino/f017458c29d0b98eeaf239529f04af22 to your computer and use it in GitHub Desktop.
Scala秋祭り2019 発表資料内に登場するソースコードの元
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_![R](s: String): 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
// ===
// Scala 2.13.0 より Using が入った
def resource[R, A](a: => A)(implicit F: Releasable[A]): Cont[R, A] =
Cont(ar => Using.resource(a)(ar))
// ===
// ===
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
} yield names
scala> prog.run(_.mkString(", "))
res133: Result = query execution error
// 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))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment