On Thu, Feb 18, 2016 at 05:06:22AM -0800, scala solist wrote:
> I'm using Either to represent a calculation that could fail. But there are
> two general ways for a calculation to fail: soft and hard. In the first case
> the caller could recover computing with reasonable default. In the second
> something horrible occurred that defeat the purpose of all calculation.
This sounds like it could get messy very quickly. Your design also sets policy
in that a soft-failed computation cannot return a value, whereas it can still
be reasonable to return a best-efforts result along with a diagnostic. It's
also unclear what you intend to do with the soft-fail results. If you are
skipping over them, you don't need a Either[Soft, Success] and can just use an
Option[Success].
If you are determined to write this in pure functional form, your proposed
`Computation` trait looks less likely to get messy in use than nested
`Either`s. If nothing else, it is a clean slate to fill with helper methods,
whereas you're stuck with `Either`'s existing API. Try to aim for mechanism
rather than policy when designing your API.
When I've had similar requirements, I've taken a more pragmatic approach and
not bothered with monads at all. Hard failures are signalled by throwing an
exception. Soft failures are recorded by an instance of a custom `Warning`
trait, which is passed into code that may soft fail using implicit parameters.
Computation results are returned unwrapped. We're on the JVM, it's designed for
this.
My `Warning` is typically a type alias for or subclass of `String => Unit`, the
threaded variable called `warn`, and so code that signals soft failure is
written `warn("something's wrong")`. Concrete implementations of `Warning` turn
soft-fails into an exception during development, and log them in production.
This is all nice and simple, for which any maintenance programmer which comes
afterwards will be thankful.