Calling the chaining method with

I would like to know if it is possible to create some kind of "chain of method calls", while all methods return the same way. [Error, Result].

What I would like to do is: call all methods sequentially, and when the method returns Left (Error), then stop the method calls and return the first left ones found in the call chain.

I tried something with a fold, map, projections ... but I am new to Scala and have not found anything elegant solution.

I tried something like this:

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 that will not work. And in any case, I really don't like the indentation. Also, I would like to convert Error to a new line describing the problem (I think I should use fold?)

I am looking for something like this:

 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 options and option?

One limitation will also be that if the first call is not being made, other calls should not be made. I do not want a solution where I call everything and then join eithers.

Thank!

+8
scala functional-programming monads either
Aug 24 '12 at 10:05
source share
3 answers

There are better, more functional ways to do this (mainly by checking Scalazs Validation and traverse / sequence), but your code is roughly equivalent:

 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 nesting. Since Scala s Either is incorrectly biased by default, you must specify this manually several times, which slightly reduces readability.

+11
Aug 24 '12 at 10:35
source share

The RightProjection that you are already using allows you to do exactly what you need using its flatMap method.

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

Actually, what we have here is that RightProjection forms a monad. You can convert the value of x to the projection using Right(x).right . And if you have a projection p , you can apply a possible failed calculation of f to p by calling p.flatMap(f) . Thus, you can link several such methods.

This can be further simplified by using for concepts. 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 result:

 -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) 
+5
Aug 24 '12 at 12:20
source share

Debilski has a “response” in functionality, but I would cut it off with 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 case, there is no need to register successful creation events (only failures), since the entire transaction is rolled back on failure, but YMMV adds to the log as you like.

+2
Aug 24 '12 at 11:11
source share



All Articles