Question

I'd like to know if it is possible to create some kind of "method call chain", with all methods returning the same Either[Error,Result].

What i'd like to do is: call all the methods successively, and when method returns a Left(Error), then stop the method calls and return the first Left found in the call chain.

I've tryied some stuff, with fold, map, projections... but i'm new to Scala and don't find any elegant solution.

I've tryed some thing like that:

def createUserAndMandatoryCategories(user: User) : Either[Error,User] = {
    User.create(user).right.map {
      Logger.info("User created")
      Category.create( Category.buildRootCategory(user) ).right.map {
        Logger.info("Root category created")
        Category.create( Category.buildInboxCategory(user) ).right.map {
          Logger.info("Inbox category created")
          Category.create( Category.buildPeopleCategory(user) ).right.map {
            Logger.info("People category created")
            Category.create( Category.buildTrashCategory(user) ).right.map {
              Logger.info("Trash category created")
              Logger.info("All categories successfully created created")
              Right(user)
            }
          }
        }
      }
    }
  }

But it doesn't work. And anyway i really don't like the indentation it takes. Besides i'd like to transform the Error into a new String describing the problem (i guess i should use fold?)

I'm looking for something written like that:

val result : Either[String,CallResult] = call1.something("error 1 description")
.call2.something("error 2 description")
.call3.something("error 3 description")
.call4.something("error 4 description")

Is it possible to do such a thing with Scala? Perhaps using both Either and Option?

One constraint would also be that if the first call fails, the other calls should not be made. I don't want a solution where i call everything and then join the eithers.

Thanks!

Was it helpful?

Solution

There are better, more functional ways to do this (mostly involving Scalaz’s Validation and traverse/sequence) but your code is roughly equivalent to:

def createUserAndMandatoryCategories(user: User) : Either[Error,User] = for {
  _ <- User.create(user).right.map(Logger.info("User created")).right
  _ <- Category.create( Category.buildRootCategory(user) ).right.map(Logger.info("Root category created")).right
  _ <- Category.create( Category.buildInboxCategory(user) ).right.map(Logger.info("Inbox category created")).right
} yield user

Which at least gets rid of all the nesting. Since Scala’s Either is not right-biased by default, you have to specify that manually quite a few times, which reduces the readability a bit.

OTHER TIPS

The RightProjection you're already using allows you to do exactly what you need using its flatMap method.

(By convention, computation results are stored in Right and error values for failed computations in Left. But there is no other reason, you could do the same with LeftProjection.)

Actually, what we have here is that RightProjection forms a monad. You can convert a value x into a projection using Right(x).right. And if you have a projection p, you can apply a possibly failing computation f on p by calling p.flatMap(f). This way, you can chain several such methods.

This can be further simplified by for comprehensions. To give a complete example:

object EitherTest extends App {
  // we define some methods that can either fail 
  // and return a String description of the error,
  // or return a value

  def sqrt(x: Double): Either[String,Double] =
    if (x >= 0) Right(math.sqrt(x));
    else Left("Negative value " + x + " cannot be square-rooted.");

  // or you could have, if you want to avoid typing .right inside `for` later
  def sqrt0(x: Double): Either.RightProjection[String,Double] =
    ( if (x >= 0) Right(math.sqrt(x));
      else Left("Negative value " + x + " cannot be square-rooted.")
    ).right;

  def asin(x: Double): Either[String,Double] =
    if (x > 1) Left("Too high for asin")
    else if (x < -1) Left("Too low for asin")
    else Right(math.asin(x));


  // Now we try to chain some computations.
  // In particular, we'll be computing sqrt(asin(x)).
  // If one of them fails, the rest will be skipped
  // and the error of the failing one will be returned
  // as Left.

  { // try some computations
    for(i <- -5 to 5) {
      val input: Double = i / 4.0;
      val d: Either[String,Double] = Right(input);
      val result: Either[String,Double] =
        for(v <- d.right;
            r1 <- asin(v).right;
            r2 <- sqrt(r1).right
            // or you could use:
            // r2 <- sqrt0(r1)
        ) yield r2;
      println(input + "\t->\t" + result);
    }
  }
}

And the output is:

-1.25       ->      Left(Too low for asin)
-1.0        ->      Left(Negative value -1.5707963267948966 cannot be square-rooted.)
-0.75       ->      Left(Negative value -0.848062078981481 cannot be square-rooted.)
-0.5        ->      Left(Negative value -0.5235987755982989 cannot be square-rooted.)
-0.25       ->      Left(Negative value -0.25268025514207865 cannot be square-rooted.)
0.0         ->      Right(0.0)
0.25        ->      Right(0.5026731096270007)
0.5         ->      Right(0.7236012545582677)
0.75        ->      Right(0.9209028607738609)
1.0         ->      Right(1.2533141373155001)
1.25        ->      Left(Too high for asin)

Debilski has "the" answer in going functional, but I'd trim it down further with some helper code:

// trait PackageBase (applicable package objects extend)
/*
 * not used in this example but can use below implicit to do something like:
 * for { x <- eitherResult as json }
 */
class RightBiasedEither[A,B](e: Either[A,B]) {
  def as[A1](f: A => A1) = e match {
    case Left(l)    => Left(f(l)).right
    case Right(r) => Right(r).right
  }
}
@inline implicit final def either2Projection[L,R](e: Either[L,R]) = new RightBiasedEither(e)

class Catching[T](f: => T) extends grizzled.slf4j.Logging {
  def either(msg: String) = { // add your own logging here
    try { Right(f).right }
    catch { case e: Exception => error( e.getMessage ); Left(msg).right }
  }
}
def catching[T](f: => T) = new Catching(f)

// in your query wrapper equivalent
protected def either[T](result: => T, msg: String)(implicit ss: Session) = {
  catching(result) either(msg)
}

// and then your DAO create methods will do something like:
def create(foo: Foo)(implicit ss: Session) {
  either[Int]( Foos.insert(foo), i18n("not created") )
}

// with the above code you can then strip things down to:
def createUserAndMandatoryCategories(user: User) : Either[Error,User] = {
  db.handle withSession { implicit ss: Session =>
    ss.withTransaction { 
      val result = for {
        _ <- User.create(user)
        _ <- Category.create( Category.buildRootCategory(user) )
        _ <- Category.create( Category.buildInboxCategory(user) )
      } yield user
      result fold ( e => { ss.rollback; Left(e) }, u => Right(u) )
    }
  }
}

In my take there is no need to log successful creation events (only the failures) since the entire transaction is rolled back on failure, but YMMV, add in logging as you like.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top