Suppose I define an instance of the Monad
typeclass for Future
:
val futureMonad = new Monad[Future] {
override def point[A](a: ⇒ A): Future[A] =
Future(a)
override def bind[A, B](fa: Future[A])(f: A => Future[B]): Future[B] =
fa flatMap f
}
Strictly speaking, this is not a monad, since it violates the law of left identity:
futureMonad.point(a) bind f == f(a)
If f
throws an exception, the result of the expression on the left hand side will be a failed Future
, whereas the right hand side will, of course, throw the exception.
But what are the practical implications of this violation? In which ways can a system fail as a result of this "misbehavior"?
It just means, in terms of for comprehensions, that the following refactoring is not semantics-preserving:
for (fut <- Future(a); x <- f(fut)) yield x ==> f(a)
But that's just another way of writing the left identity law, really.
To explain that invalid refactoring further:
for (fut <- Future(a); x <- f(fut)) yield x
==> for (x <- f(a)) yield x // by left identity law: WRONG, because left identity law does not hold
==> f(a) // by 1st functor law: WRONG, because previous line was wrong
Monads such as Try
and Future
trade one monad law for another law which is more useful in the context they are supposed to be used:
An expression composed from (Try
or Future
), flatMap, map will never throw a non-fatal exception. Call this the "bullet-proof" principle.
So actually this approach really protects you against many failures and left-unit law is failed deliberately.