我想知道是否可以创建某种“方法调用链”,所有方法都返回相同的[错误,结果]。
我想做的是:连续调用所有方法,当方法返回Left(Error)时,停止方法调用并返回调用链中找到的第一个Left。
我已尝试过一些东西,包括折叠,地图,投影......但我是Scala的新手,并没有找到任何优雅的解决方案。
我尝试过这样的事情:
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)
}
}
}
}
}
}
但它不起作用。 无论如何,我真的不喜欢它所需的缩进。 此外,我想将Error转换为描述问题的新String(我想我应该使用fold?)
我正在寻找类似的东西:
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")
用Scala做这样的事情有可能吗?也许同时使用Either和Option?
一个限制也是如果第一次调用失败,则不应该进行其他调用。我不想要一个解决方案,我称之为一切,然后加入eithers。
谢谢!
答案 0 :(得分:11)
有更好,更实用的方法(主要涉及Scalaz的验证和遍历/序列),但您的代码大致相当于:
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
至少摆脱了所有的筑巢。由于Scala的Either
默认情况下没有右偏,因此您必须手动指定相当多次,这会略微降低可读性。
答案 1 :(得分:6)
您已经使用的RightProjection允许您使用其flatMap
方法完成所需的操作。
(按照惯例,计算结果存储在Right
和Left
中失败计算的错误值。但没有其他原因,您可以对LeftProjection
执行相同的操作。)
实际上,我们在这里有RightProjection
形成一个monad。您可以使用x
将值Right(x).right
转换为投影。如果您有投影p
,则可以通过调用f
在p
上应用可能失败的计算p.flatMap(f)
。这样,您可以链接几个这样的方法。
这可以通过for
理解进一步简化。举一个完整的例子:
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);
}
}
}
输出是:
-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)
答案 2 :(得分:2)
Debilski在功能上有“答案”,但我会用一些帮助代码进一步修改它:
// 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) )
}
}
}
在我看来,没有必要记录成功的创建事件(只有失败),因为整个事务在失败时回滚,但是YMMV,根据需要添加日志记录。