给出定义:
trait Functor[F[_]]:
extension [A](fa: F[A]) def map[B](f: A => B): F[B]
trait Monad[F[_]] extends Functor[F]:
def unit[A](a: => A): F[A]
extension [A](fa: F[A])
def flatMap[B](f: A => F[B]): F[B]
def map[B](f: A => B): F[B] =
flatMap(f.andThen(unit))
object MonadSyntax:
// 'fA' is the thing on which the method >>= is invoked.
extension [F[_], A](fA: F[A])(using mA: Monad[F])
def >>=[B](f: A => F[B]): F[B] = mA.flatMap(fA)(f)
我想使用 ScalaTest 和 ScalaCheck 编写测试来验证 Monad 三个定律。我不想对每个 Monad 实例重复测试,而是想让测试变得通用。我的尝试如下:
trait MonadLaws[F[_]] { this: AnyFunSpec & ScalaCheckPropertyChecks =>
// Monad[F].unit(x).flatMap(f) === f(x)
def leftIdentity[A: Arbitrary, B: Arbitrary](
using arbAToFB: Arbitrary[A => F[B]],
ma: Monad[F]
): Unit =
it("should satisfy the left identity law"):
forAll { (x: A, f: A => F[B]) =>
(ma.unit(x) >>= f) shouldBe f(x)
}
// m.flatMap(Monad[F].unit) === m
def rightIdentity[A: Arbitrary](using eqM: Equality[Monad[F]], ma: Monad[F]): Unit =
it("should satisfy the right identity law"):
val left = for
a: A <- ma
yield summon[Monad[F]].unit(a)
left shouldBe ma
// m.flatMap(f).flatMap(g) === m.flatMap { x => f(x).flatMap(g) }
def associativityLaw[A: Arbitrary, B: Arbitrary, C: Arbitrary](
using arbAToFB: Arbitrary[A => F[B]],
arbBToFB: Arbitrary[B => F[C]],
ma: Monad[F],
eqM: Equality[Monad[F]]
): Unit =
it("should satisfy the associativity law"):
forAll { (f: A => F[B], g: B => F[C]) =>
ma.flatMap(f).flatMap(g) shouldBe m.flatMap(x => f(x).flatMap(g))
}
}
然后一个特定的 Monad 实例将作为测试运行,如下所示:
class OptionMonadLawsSpec extends AnyFunSpec with ScalaCheckPropertyChecks with MonadLaws[Option]:
describe("Options form a Monad"):
import MonadInstances.optionMonad
leftIdentity[Int, Int]
为简洁起见,省略了导入。
问题是第二个和第三个测试无法编译,都失败了:
Found: A => F[A]
Required: F[A²]
where: A is a type in method rightIdentity
A² is a type variable with constraint
F is a type in trait MonadLaws with bounds <: [_] =>> Any
我绞尽脑汁一个多小时了,还是没能把类型搞对。另外,如何调用
Monad[F].unit
,其中 unit
是实例方法?
@LuisMiguelMejíaSuárez 在 Typelevel Discord 频道上帮助我编译代码。我还没有彻底测试它,但类型检查出来了,他也很友善地指出了一个基本的误解。
trait MonadLaws[F[_]] { this: AnyFunSpec & ScalaCheckPropertyChecks =>
// Monad[F].unit(x).flatMap(f) === f(x)
def leftIdentity[A, B](using
Monad[F],
Arbitrary[A],
Arbitrary[A => F[B]],
Equality[F[B]]
): Unit =
it("should satisfy the left identity law"):
forAll { (a: A, f: A => F[B]) =>
val lhs = summon[Monad[F]].unit(a) >>= f
val rhs = f(a)
lhs shouldBe rhs
}
// m.flatMap(Monad[F].unit) === m
def rightIdentity[A, B](using Monad[F], Arbitrary[F[A]], Equality[F[A]]): Unit =
it("should satisfy the right identity law"):
forAll { (fa: F[A]) =>
val lhs = fa >>= summon[Monad[F]].unit
val rhs = fa
lhs shouldBe rhs
}
// m.flatMap(f).flatMap(g) === m.flatMap { x => f(x).flatMap(g) }
def associativityLaw[A, B, C](using
Monad[F],
Arbitrary[F[A]],
Arbitrary[A => F[B]],
Arbitrary[B => F[C]],
Equality[F[C]]
): Unit =
it("should satisfy the associativity law"):
forAll { (fa: F[A], f: A => F[B], g: B => F[C]) =>
val lhs = fa >>= f >>= g
val rhs = fa >>= (a => f(a) >>= (g))
lhs shouldBe rhs
}
}