测试副作用monad的定律

lam*_*das 7 monads functional-programming scala equality scalaz

我正在编写一个库来通过API访问Web服务.我已经定义了简单的类来表示API动作

case class ApiAction[A](run: Credentials => Either[Error, A])
Run Code Online (Sandbox Code Playgroud)

和一些执行Web服务调用的函数

// Retrieve foo by id
def get(id: Long): ApiAction[Foo] = ???

// List all foo's
def list: ApiAction[Seq[Foo]] = ???

// Create a new foo
def create(name: String): ApiAction[Foo] = ???

// Update foo
def update(updated: Foo): ApiAction[Foo] = ???

// Delete foo
def delete(id: Long): ApiAction[Unit] = ???
Run Code Online (Sandbox Code Playgroud)

我也做ApiAction了一个单子

implicit val monad = new Monad[ApiAction] { ... }
Run Code Online (Sandbox Code Playgroud)

所以我可以做点什么

create("My foo").run(c)
get(42).map(changeFooSomehow).flatMap(update).run(c)
get(42).map(_.id).flatMap(delete).run(c)
Run Code Online (Sandbox Code Playgroud)

现在我有麻烦测试它的monad法律

val x = 42
val unitX: ApiAction[Int] = Monad[ApiAction].point(x)

"ApiAction" should "satisfy identity law" in {
  Monad[ApiAction].monadLaw.rightIdentity(unitX) should be (true)
}
Run Code Online (Sandbox Code Playgroud)

因为monadLaw.rightIdentity用途equal

def rightIdentity[A](a: F[A])(implicit FA: Equal[F[A]]): Boolean = 
  FA.equal(bind(a)(point(_: A)), a)
Run Code Online (Sandbox Code Playgroud)

而且没有Equal[ApiAction].

[error] could not find implicit value for parameter FA: scalaz.Equal[ApiAction[Int]]
[error]     Monad[ApiAction].monadLaw.rightIdentity(unitX) should be (true)
[error]                                            ^
Run Code Online (Sandbox Code Playgroud)

问题是我甚至无法想象如何定义它Equal[ApiAction].ApiAction本质上是一个函数,我不知道函数的任何相等关系.当然可以比较跑步ApiAction的结果,但不一样.

当我做一些非常错误的事情或者不理解某些必要的东西时,我觉得这样.所以我的问题是:

  • 是否是有意义的ApiAction是一个单子?
  • 我设计得ApiAction对吗?
  • 我应该如何测试其monad法律?

Jam*_*pic 4

ApiAction我将从简单的开始:是的,成为一个单子是有意义的。是的,你已经以合理的方式设计了它 - 这个设计看起来有点像IOHaskell 中的 monad。

棘手的问题是你应该如何测试它。

唯一有意义的等式关系是“给定相同的输入产生相同的输出”,但这只是在纸面上真正有用,因为计算机不可能验证,并且它只对纯函数有意义。事实上,Haskell 的IOmonad 与您的 monad 有一些相似之处,但没有实现Eq. 因此,如果您不实施Equal[ApiAction].

尽管如此,对于实现仅在测试中使用的特殊实例可能存在争议Equal[ApiAction],该实例使用硬编码Credentials值(或少量硬编码值)运行操作并比较结果。从理论的角度来看,这很糟糕,但从实用的角度来看,它并不比使用测试用例进行测试更糟糕,并且可以让您重用 Scalaz 中的现有辅助函数。

另一种方法是忘记 Scalaz,ApiAction使用铅笔和纸证明满足单子定律,并编写一些测试用例来验证一切是否按照您认为的方式工作(使用您编写的方法,而不是那些方法)来自斯卡拉兹)。事实上,大多数人都会跳过纸笔步骤。