Skip to content

Instantly share code, notes, and snippets.

@machuz
Last active May 25, 2018 05:25
Show Gist options
  • Save machuz/fe8b063fcbea56fbf46d25c81f2a67d0 to your computer and use it in GitHub Desktop.
Save machuz/fe8b063fcbea56fbf46d25c81f2a67d0 to your computer and use it in GitHub Desktop.
scalacheckのmonad.lawsで自作Maybeのテスト
import scalaz.Functor
// Functor(F)と、計算値(A)を型パラメータでとる
// mapとflatMapを持ち、Pureなら関数適用、InpureならFunctorを使ってFの計算値(A)にfを適用する
sealed abstract class Free[F[_], A] {
import Free._
def map[B](f: A => B)(implicit F: Functor[F]): Free[F, B] =
flatMap(a => Free.Pure(f(a)))
def flatMap[B](f: A => Free[F, B])(implicit F: Functor[F]): Free[F, B] =
this match {
case Free.Pure(a) => f(a)
case Free.Impure(ff) => Free.Impure(F.map(ff)(_.flatMap(f)))
}
}
object Free {
// Freeの状態クラス
// scalazのFreeはユーザーの入力待時などに一時計算を止めるsuspend,resumeを
// 考慮した作りになっていて便利だが煩雑なのでresumeを考慮しないサンプルを書く
case class Pure[F[_], A](a: A) extends Free[F, A]
case class Impure[F[_], A](ff: F[Free[F, A]]) extends Free[F, A]
def lift[F[_], A](fa: F[A])(implicit F: Functor[F]): Free[F, A] =
Impure(F.map(fa)(a => Pure(a)))
//関数を適用して、`implicit F: Functor[F]`を明示的に渡している。※FreeはFunctorを持っているのでここではFreeをわたす
}
trait Maybe[+A] extends Functor[Maybe] {
def isEmpty: Boolean
def get: A
def map[A, B](fa: Maybe[A])(f: A => B): Maybe[B] =
if (fa.isEmpty) Empty else Just(f(fa.get))
}
final case class Just[+A](x: A) extends Maybe[A] {
def isEmpty = false
def get = x
}
case object Empty extends Maybe[Nothing] {
def isEmpty = true
def get = throw new NoSuchElementException("Empty.get")
}
implicit val MaybeFunctor: Functor[Maybe] =
new Functor[Maybe] {
def map[A, B](fa: Maybe[A])(f: A => B): Maybe[B] =
if (fa.isEmpty) Empty else Just(f(fa.get))
}
type MaybeM[A] = Free[Maybe, A]
trait MaybeMInstances {
implicit val maybeMInstance = new scalaz.Monad[MaybeM] {
// (a) 注入関数の実装
def point[A](x: => A): MaybeM[A] = MaybeM.just(x)
/**
* (b) 連鎖関数の実装
*
* b1. Counter モナド fa からタプルの内容を取得
* b2. x の値に f を適用した結果の Counter モナドからタプルの内容を取得
* b3. y の値とカウンター値 d に fa のカウンター値 c を加算した値を持つ Counter モナドを返す
*/
def bind[A, B](fa: MaybeM[A])(f: (A) => MaybeM[B]): MaybeM[B] = {
fa.flatMap(f)
}
}
}
// (2) Monad のインスタンスを定義
case object MaybeM extends MaybeMInstances {
def just[A](a: A): MaybeM[A] = Free.Pure(a)
def empty[A]: MaybeM[A] = Free.lift[Maybe, A](Empty)
}
val r = for {
x <- MaybeM.just(1)
y <- MaybeM.just(2)
z <- MaybeM.empty[Int]
} yield x + y + z
implicit def maybeEqual[A]: Equal[MaybeM[A]] = Equal.equalA
implicit val maybeFunctor = new Functor[MaybeM] {
def map[A, B](fa: MaybeM[A])(f: A => B): MaybeM[B] = fa match {
case Free.Impure(_) => MaybeM.empty
case Free.Pure(c) => MaybeM.just(f(c))
}
}
// monad則をちぇっく
import scalaz._, Scalaz._, scalacheck.ScalazProperties._, scalacheck.ScalazArbitrary._, scalacheck.ScalaCheckBinding._
import org.scalacheck.{ Gen, Arbitrary }
implicit def maybeMArbiterary[A](implicit a: Arbitrary[A]): Arbitrary[MaybeM[A]] =
a.map { a =>
MaybeM.just(a): MaybeM[A]
}
implicit val mm = MaybeM.maybeMInstance
monad.laws[MaybeM].check
// + monad.applicative.apply.functor.invariantFunctor.identity: OK, passed 100
// tests.
// + monad.applicative.apply.functor.invariantFunctor.composite: OK, passed 10
// 0 tests.
// + monad.applicative.apply.functor.identity: OK, passed 100 tests.
// + monad.applicative.apply.functor.composite: OK, passed 100 tests.
// + monad.applicative.apply.composition: OK, passed 100 tests.
// + monad.applicative.identity: OK, passed 100 tests.
// + monad.applicative.homomorphism: OK, passed 100 tests.
// + monad.applicative.interchange: OK, passed 100 tests.
// + monad.applicative.map consistent with ap: OK, passed 100 tests.
// + monad.bind.apply.functor.invariantFunctor.identity: OK, passed 100 tests.
// + monad.bind.apply.functor.invariantFunctor.composite: OK, passed 100 tests
// .
// + monad.bind.apply.functor.identity: OK, passed 100 tests.
// + monad.bind.apply.functor.composite: OK, passed 100 tests.
// + monad.bind.apply.composition: OK, passed 100 tests.
// + monad.bind.associativity: OK, passed 100 tests.
// + monad.bind.ap consistent with bind: OK, passed 100 tests.
// + monad.right identity: OK, passed 100 tests.
// + monad.left identity: OK, passed 100 tests.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment