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:
- Does it makes sense for
ApiAction
to be a monad? - Have I designed
ApiAction
right? - How should I test its monad laws?
run
an abstract method of a sealed trait, and then implement yourApiAction
s as derived case classes and case objects. – Snapper