I'm writing a library to access web service through the API. I've defined simple class to represent API action
case class ApiAction[A](run: Credentials => Either[Error, A])
and some functions that performs 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've also made ApiAction
a monad
implicit val monad = new Monad[ApiAction] { ... }
So 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 troubles testing its 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 there is 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 I can't even imagine how it could be possible to define Equal[ApiAction]
. ApiAction
is essentialy a function, and I don't know of any equality relation on functions. Of course it is possible to compare results of running ApiAction
's, but it is not the same.
I feel as I doing something terribly wrong or don't understand something essential. So my questions are:
ApiAction
to be a monad?ApiAction
right?I'll start with the easy ones: Yes, it makes sense for ApiAction
to be a monad. And yes, you've designed it in a reasonable way - this design looks a bit like the IO
monad in Haskell.
The tricky question is how you should test it.
The only equality relation that makes sense is "produces same output given same input", but that's only really useful on paper, since it's not possible for a computer to verify, and it's only meaningful for pure functions. Indeed, Haskell's IO
monad, which has some similarities to your monad, doesn't implement Eq
. So you're probably on safe ground if you don't implement Equal[ApiAction]
.
Still, there might be an argument for implementing a special Equal[ApiAction]
instance for use solely in tests, that runs the action with a hard-coded Credentials
value (or a small number of hard-coded values) and compares the results. From a theoretical point of view, it's just awful, but from a pragmatic point of view it's no worse than testing it with test cases, and lets you re-use existing helper functions from Scalaz.
The other approach would be to forget about Scalaz, prove ApiAction
satisfies the monad laws using pencil-and-paper, and write some test cases to verify that everything works the way you think it does (using the methods you've written, not the ones from Scalaz). Indeed, most people would skip the pencil-and-paper step.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With