Normalization of types of validation errors

I am trying to find the most convenient way to normalize error types in understanding on scalaz.Validations in one "upper" layer, which calls methods that return checks with different types of errors at the "lower" levels. Naturally, a mapping must be defined for each type of lower-level error for the type of upper-level error, but I can not do without providing explicit type hints inside concepts so that my implicits are matched. I would like to leave every line of understanding clear for explicitly converting the type of error (for example, by checking .fail.map (...)). I tried an approach with a trait like IsUpperError and an implicit conversion as follows:

package errorhandling

import scalaz._
import Scalaz._

class LowerServiceA {
  def doStuff(): Validation[LowerServiceAError, Int] = Success(1)
}
sealed trait LowerServiceAError
// ... more specific error types

class LowerServiceB {
  def doStuff(): Validation[LowerServiceBError, Int] = Success(1)
}
sealed trait LowerServiceBError
// ... more specific error types

class LowerServiceC {
  def doStuff(): Validation[LowerServiceCError, Int] = Success(1)
}
sealed trait LowerServiceCError
// ... more specific error types

sealed trait UpperError {}
// ... more specific error types
trait IsUpperError[E] {
  def apply(e: E): UpperError
}
object IsUpperError {
  implicit val lowerServiceAErrorIsUpperError: IsUpperError[LowerServiceAError] = new IsUpperError[LowerServiceAError] {
    def apply(err: LowerServiceAError) = new UpperError {}
  }

  implicit val lowerServiceBErrorIsUpperError: IsUpperError[LowerServiceBError] = new IsUpperError[LowerServiceBError] {
    def apply(err: LowerServiceBError) = new UpperError {}
  }

  implicit val lowerServiceCErrorIsUpperError: IsUpperError[LowerServiceCError] = new IsUpperError[LowerServiceCError] {
    def apply(err: LowerServiceCError) = new UpperError {}
  }
}

object UpperError {
  implicit def upperError[E: IsUpperError, A](v: Validation[E, A]): Validation[UpperError, A] =
    v.fail.map(e => implicitly[IsUpperError[E]].apply(e)).validation
}

class UpperService(serviceA: LowerServiceA, serviceB: LowerServiceB, serviceC: LowerServiceC) {
  def doStuff(): Validation[UpperError, Int] = {
    for {
      // I'd like to avoid the repeated type-hints or .fail.map(...).validation here
      a <- serviceA.doStuff() // : Validation[UpperError, Int]
      b <- serviceB.doStuff() // : Validation[UpperError, Int]
      c <- serviceC.doStuff()
    } yield a + b + c
  }
}

Compiler error (2.9.2) without hints like:

ErrorHandling.scala:56: error: could not find implicit value for evidence parameter of type errorhandling.IsUpperError[java.lang.Object]
  b <- serviceB.doStuff() //: Validation[UpperError, Int]
    ^

one error found

, , .

+3

All Articles