Testing the laws of the bonus monad

I am writing a library to access a web service through an API. I defined a simple class to represent an API action

case class ApiAction[A](run: Credentials => Either[Error, A]) 

and some functions that make web service calls

 // 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] = ??? 

I also made ApiAction monad

 implicit val monad = new Monad[ApiAction] { ... } 

So that I could do something like

 create("My foo").run(c) get(42).map(changeFooSomehow).flatMap(update).run(c) get(42).map(_.id).flatMap(delete).run(c) 

Now I have problems testing their monad laws.

 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) } 

because monadLaw.rightIdentity uses equal

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

and no 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] ^ 

The problem is that I canโ€™t even imagine how Equal[ApiAction] could be defined. ApiAction is an essential function, and I do not know any relation of equality in functions. Of course, you can compare the results of ApiAction , but this is not the same.

I feel that I am doing something terribly wrong or do not understand something significant. So my questions are:

  • Does ApiAction make ApiAction as a monad?
  • Did I create ApiAction ?
  • How to check your monad laws?
+7
equality scala functional-programming monads scalaz
source share
3 answers

I'll start with the simple ones: yes, it makes sense for ApiAction to be a monad. And yes, you designed it in a smart way - this design is a bit like the IO monad in Haskell.

The hard question is how you should test it.

The only relation of equality that makes sense is โ€œproduces the same conclusion as the same input,โ€ but it is really useful on paper because it is impossible to check the computer, and it only makes sense for pure functions. Indeed, the Haskell IO monad, which has some similarities with your monad, does not implement Eq . Thus, you are probably in a safe place if you do not implement Equal[ApiAction] .

However, there may be an argument for implementing a special instance of Equal[ApiAction] for use exclusively in tests that triggers an action with a hard-coded value of Credentials (or a small number of hard-coded values) and compares the results. From a theoretical point of view, this is just awful, but from a pragmatic point of view it is no worse than testing it with test cases, and allows you to reuse existing auxiliary functions from Scalaz.

Another approach is to forget about Scalaz, prove that ApiAction complies with the monad laws using pencil and paper, and write down some test cases to make sure everything works the way you think (using methods, not from Scalaz) . Indeed, most people have skipped a pencil-paper step.
+4
source share

This boils down to the fact that lambdas are anonymous subclasses of FunctionN, where you only have equality of instances, so only the same subclass of anonymus is equal to itself.

One idea on how you could do this: Make your operations concrete subclasses of Function1 instead of instances:

 abstract class ApiAction[A] extends (Credentials => Either[Error, A]) // (which is the same as) abstract class ApiAction[A] extends Function1[Credentials, Either[Error, A]] 

This allows you, for example, to create case objects for your instances.

 case class get(id: Long) extends ApiAction[Foo] { def apply(creds: Credentials): Either[Error, Foo] = ... } 

and this, in turn, should allow you to implement equal for each ApiAction subclass in a way that suits you, for example, according to the parameters of the constructor. (You can get it for free by doing operations classes like me)

 val a = get(1) val b = get(1) a == b 

You can also do this without extending Function1, as I did, and use the launch box as you did, but this method gave the most succinic example code.

+1
source share

I think you could implement it with the disadvantage of using a macro or reflection to wrap your functions in a class that includes the AST. You can then compare the two functions by comparing their AST.

See What is the easiest way to use a reify (get AST) expression in Scala?

0
source share

All Articles