Skip to content

Instantly share code, notes, and snippets.

@aoiroaoino
Last active October 26, 2019 07:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aoiroaoino/501a0fc0bedd4c631ed09dfa24d770a6 to your computer and use it in GitHub Desktop.
Save aoiroaoino/501a0fc0bedd4c631ed09dfa24d770a6 to your computer and use it in GitHub Desktop.
Scala関西Summit 2019 登壇資料の Appendix

Scala関西Summit 2019 登壇資料の Appendix

スライド

イベント

動作確認環境

  • Scala 2.13.1
  • sbt 1.3.3
  • ScalaCheck 1.14.1

Tips

モナド則を満たさない例

scala> val law = new Monad.Law[Try] { val M = fn.data.ScalaTry.tryInstances }
law: fn.Monad.Law[scala.util.Try]{val M: fn.Monad[scala.util.Try]} = $anon$1@45f35b79

scala> law.leftIdentity("42", (s: String) => Try(s.toInt))
res0: Boolean = true

scala> law.leftIdentity("Hello", (s: String) => Try(s.toInt))
res1: Boolean = false

参考資料

(敬称略)

val xs: Either[String, Int] = Right(1)
val ys: Either[String, Int] = Right(2)
for {
x <- xs
y <- ys
} yield x + y
// res0: scala.util.Either[String,Int] = Right(3)
val xs: List[String] = List("foo", "bar")
val ys: List[String] = List("!", "?")
for {
x <- xs
y <- ys
} yield x + y
// res3: List[String] = List(foo!, foo?, bar!, bar?)
for {
x <- expr1
y <- expr2
} yield {
expr3
}
expr1.flatMap { x =>
expr2.map { y =>
expr3
}
}
// M[_] はモナドの型
def pure[A](a: A): M[A]
def flatMap[B](f: A => M[B]): M[B]
// デフォルト実装が与えられる
def map[B](f: A => B): M[B] = flatMap(a => pure(f(b)))
// モナド則
// 左単位元
pure(a).flatMap(f) == f(a)
// 右単位元
fa.flatMap(a => pure(a)) == fa
// 結合律
fa.flatMap(f).flatMap(g) == fa.flatMap(a => f(a).flatMap(g))
Option("42").flatMap(_.toIntOption)
// res25: Option[Int] = Some(42)
for {
i <- List(1, 2, 3)
j <- List(1, -1)
} yield i * j
// res26: List[Int] = List(1, -1, 2, -2, 3, -3)
trait Monad[F[_]] {
def pure[A](a: A): F[A]
def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B]
// map のデフォルト実装
def map[A, B](fa: F[A])(f: A => B): F[B] = flatMap(fa)(a => pure(f(a))
}
object Monad {
// implicitly[Monad[F]] のエイリアス
def apply[F[_]](implicit M: Monad[F]): Monad[F] = M
}
trait MonadLaw[F[_]] {
def M: Monad[F]
def leftIdentity[A, B](a: A, f: A => F[B]): Boolean =
M.flatMap(M.pure(a))(f) == f(a)
def rightIdentity[A](fa: F[A]): Boolean =
M.flatMap(fa)(M.pure) == fa
def associativity[A, B, C](fa: F[A], f: A => F[B], g: B => F[C]): Boolean =
M.flatMap(M.flatMap(fa)(f))(g) == M.flatMap(fa)(a => M.flatMap(f(a))(g))
}
class MonadSpec extends Properties("Monad") {
// def M: Monad[Maybe] = Maybe.maybeInstances
def law: Monad.Law[Maybe] = new Monad.Law[Maybe] { override val M = Maybe.maybeInstances }
def odd(i: Int): Maybe[Int] = if (i % 2 != 0) Just(i) else Empty()
def fizz(i: Int): Maybe[Int] = if (i % 3 == 0) Just(i) else Empty()
def buzz(i: Int): Maybe[Int] = if (i % 5 == 0) Just(i) else Empty()
property("leftIdentity") = Prop.forAll { i: Int =>
law.leftIdentity(i, fizz)
}
property("rightIdentity") = Prop.forAll { i: Int =>
law.rightIdentity(buzz(i))
}
property("associativity") = Prop.forAll { i: Int =>
law.associativity(odd(i), fizz, buzz)
}
}
sbt:fn> test
[info] + Monad.associativity: OK, passed 100 tests.
[info] + Monad.rightIdentity: OK, passed 100 tests.
[info] + Monad.leftIdentity: OK, passed 100 tests.
[info] Passed: Total 3, Failed 0, Errors 0, Passed 3
// 抽象的なモナド
sealed abstract class Maybe[A]
case class Just[A](a: A) extends Maybe[A]
case class Empty[A]() extends Maybe[A]
object Maybe {
// smart constructor
def just[A](a: A): Maybe[A] = Just(a)
def empty[A]: Maybe[A] = Empty()
}
implicit val maybeInstances: Monad[Maybe] =
new Monad[Maybe] {
def pure[A](a: A): Maybe[A] = Just(a)
def flatMap[A, B](fa: Maybe[A])(f: A => Maybe[B]): Maybe[B] =
fa match {
case Just(a) => f(a)
case Empty() => Empty()
}
}
val n = Monad[Maybe].pure(42)
Monad[Maybe].flatMap(n)(i => if (i > 0) Maybe.just(i) else Maybe.empty[Int])
scala> val n = Monad[Maybe].pure(42)
n: Maybe[Int] = Just(42)
scala> Monad[Maybe].flatMap(n) { i =>
| if (i > 0) Maybe.just(i) else Maybe.empty[Int]
| }
res7: Maybe[Int] = Just(42)
// for 式で合成ができない
for {
i <- Maybe.just(1)
j <- Maybe.just(2)
} yield i + j
// i <- Maybe.just(1)
// ^
// On line 2: error: value flatMap is not a member of Maybe[Int]
// j <- Maybe.just(2)
// ^
// On line 3: error: value map is not a member of Maybe[Int]
// Monad[Maybe].map(fa)(f) を fa.map(f) と書けるようになる
implicit class MonadSyntax[F[_], A](fa: F[A])(implicit M: Monad[F]) {
def map[B](f: A => B): F[B] = M.map(fa)(f)
def flatMap[B](f: A => F[B]): F[B] = M.flatMap(fa)(f)
}
for {
i <- Maybe.just(1)
j <- Maybe.just(2)
} yield i + j
// res1: Maybe[Int] = Just(3)
// 具体的なモナド
sealed abstract class Maybe[A] {
def flatMap[B](f: A => Maybe[B]): Maybe[B] = this match {
case Just(a) => f(a)
case Empty() => Empty()
}
// デフォルト実装で済ませる
def map[B](f: A => B): Maybe[B] = flatMap(a => Maybe.pure(f(a)))
}
case class Just[A](a: A) extends Maybe[A]
case class Empty[A]() extends Maybe[A]
object Maybe {
def pure[A](a: A): Maybe[A] = Just(a)
// smart constructor
def just[A](a: A): Maybe[A] = Just(a)
def empty[A]: Maybe[A] = Empty()
}
import fn._
for {
i <- Maybe.just(1)
j <- Maybe.just(2)
} yield i + j
// res0: Maybe[Int] = Just(3)
sealed abstract class Maybe[A] {
def flatMap[B](f: A => Maybe[B]): Maybe[B] = this match {
case Just(a) => f(a)
case e @ Empty() => e
}
def map[B](f: A => B): Maybe[B] = flatMap(a => Maybe.pure(f(a)))
}
object Maybe {
def pure[A](a: A): Maybe[A] = Just(a)
final case class Just[A](value: A) extends Maybe[A]
final case class Empty[A]() extends Maybe[A]
}
trait MonadSyntax {
implicit def toMonadOps[F[_], A](fa: F[A]): MonadOps[F, A] = new MonadOps(fa)
}
class MonadOps[F[_], A](val fa: F[A]) extends AnyVal {
def map[B](f: A => B)(implicit M: Monad[F]): F[B] = M.map(fa)(f)
def flatMap[B](f: A => F[B])(implicit M: Monad[F]): F[B] = M.flatMap(fa)(f)
}
object IO {
def pure[A](a: A): IO[A] = IO(a)
def apply[A](a: => A): IO[A] =
new IO[A] { override def unsafeRun(): A = a }
}
sealed abstract class IO[A] {
def unsafeRun(): A // 呼び出したタイミングで全ての計算が実行される
def flatMap[B](f: A => IO[B]): IO[B] =
IO { f(unsafeRun()).unsafeRun() }
def map[B](f: A => B): IO[B] =
flatMap(a => IO.pure(f(a)))
}
def readLine: IO[String] = IO { StdIn.readLine }
def println(s: String): IO[Unit] = IO { Predef.println(s) }
val io = println("Hello!")
io.unsafeRun()
scala> val io = println("Hello!")
io: fn.data.IO[Unit] = fn.data.IO$$anon$1@30609427
scala> io.unsafeRun()
Hello!
val app = for {
_ <- println("Message:")
s <- readLine
_ <- println(s"Your message is $s")
} yield s
app.unsafeRun()
scala> val app = for {
| _ <- println("Message:")
| s <- readLine
| _ <- println(s"Your message is $s")
| } yield s
app: fn.data.IO[String] = fn.data.IO$$anon$1@63a536db
scala>
scala> app.unsafeRun()
Message:
Your message is Hello!
res15: String = Hello!
final case class Reader[R, A](run: R => A) {
def flatMap[B](f: A => Reader[R, B]): Reader[R, B] =
Reader(r => f(run(r)).run(r))
def map[B](f: A => B): Reader[R, B] =
flatMap(a => Reader.pure(f(a)))
}
object Reader {
def pure[R, A](a: A): Reader[R, A] = Reader(_ => a)
}
import scala.util.chaining._
val r = for {
i <- Reader[String, Int]{ s => (s.toInt + 1).tap(println) }
j <- Reader[String, Int]{ s => (s.toInt * 10).tap(println) }
} yield i + j
// for 式での合成時には計算は行われていない
scala> val r = for {
| i <- Reader[String, Int]{ s => (s.toInt + 1).tap(println) }
| j <- Reader[String, Int]{ s => (s.toInt * 10).tap(println) }
| } yield i + j
r: fn.data.Reader[String,Int] = Reader(fn.data.Reader$$Lambda$6091/530782268@63e16a99)
// .run で引数を与えたタイミングで実行される
scala> r.run("1")
2
10
res13: Int = 12
type Async[A] = Reader[ExecutionContext, Future[A]]
def async[A](proc: => A): Async[A] = Reader(ec => Future(proc)(ec))
val a = async { println("Hello"); 42 }
a.run(scala.concurrent.ExecutionContext.global)
scala> val a = async { println("Hello"); 42 }
a: Async[Int] = Reader($$Lambda$5127/1075839271@43c6c48c)
// run を呼んだときに実行される。非同期処理の実行タイミングを制御できる
scala> a.run(scala.concurrent.ExecutionContext.global)
Hello
res1: scala.concurrent.Future[Int] = Future(<not completed>)
scala> a.run(scala.concurrent.ExecutionContext.global)
Hello
res2: scala.concurrent.Future[Int] = Future(Success(42))
trait UserRepository[F[_]] {
def resolveAll(ids: UserId*): F[Seq[User]]
}
trait MessageRepository[F[_]] {
def resolveAll(ids: Seq[MessageId]): F[Seq[Message]]
}
class GetReplyTargetUsers[F[_]: Monad](
userRepo: UserRepository[F]
messageRepo: MessageRepository[F]
){
// 前出の MonadSyntax がスコープ内に入っていること
def run(target: MessageId): F[Seq[User]] =
for {
messages <- msessageRepo.resolveAll(target)
userIds = messages.flatMap(_.replyMessages.targetUser).toSet
users <- userRepo.resolveAll(userIds)
} yield users
}
val implByIO: GetReplyTargetUsers[IO] = ???
implByIO.run(messageId)
// => IO[Seq[User]]
type Aync[A] = Reader[ExecutionContext, Future[A]]
val implByAsync: GetReplyTargetUsers[Async] = ???
implByAsync.run(messageId)
// => Async[Seq[User]]
// => Reader[ExecutionContext, Future[Seq[User]]]
val prog: IO[Option[Int]] = for {
// IO[A] の合成
nOpt <- readInt
mOpt <- readInt
} yield for {
// Option[A] の合成
n <- nOpt
m <- mOpt
} yield n + m
prog.unsafeRun()
scala> prog.unsafeRun()
// 1 と 2 を入力
res6: Option[Int] = Some(3)
scala> prog.unsafeRun()
// 1 と a を入力
res7: Option[Int] = None
final case class OptionT[F[_], A](value: F[Option[A]]) {
def map[B](f: A => B)(implicit M: Monad[F]): OptionT[F, B] =
OptionT(M.map(value)(_.map(f)))
def flatMap[B](f: A => OptionT[F, B])(implicit M: Monad[F]): OptionT[F, B] =
OptionT(M.flatMap(value) {
case Some(a) => f(a).value
case None => M.pure(None)
})
}
object OptionT {
def pure[F[_], A](a: A)(implicit M: Monad[F]): OptionT[F, A] =
OptionT(M.pure(Some(a)))
}
object IO {
// ...
implicit val ioInstances: Monad[IO] = new Monad[IO] {
override def flatMap[A, B](fa: IO[A])(f: A => IO[B]): IO[B] = fa.flatMap(f)
override def pure[A](a: A): IO[A] = IO.pure(a)
}
}
def readInt: IO[Option[Int]] = IO { StdIn.readLine().toIntOption }
val prog = for {
i <- OptionT(readInt)
j <- OptionT(readInt)
} yield i + j
scala> val prog = for {
| i <- OptionT(readInt)
| j <- OptionT(readInt)
| } yield i + j
prog: fn.data.OptionT[fn.data.IO,Int] = OptionT(fn.data.IO$$anon$1@524114c0)
scala> prog.value
res2: fn.data.IO[Option[Int]] = fn.data.IO$$anon$1@524114c0
scala> prog.value.unsafeRun()
// 1 と 2 をキーボードで入力
res3: Option[Int] = Some(3)
@aoiroaoino
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment