链接方法调用Either

Seb*_*ber 8 monads functional-programming scala either

我想知道是否有可能创建某种"方法调用链",所有方法都返回相同的[错误,结果].

我想要做的是:连续调用所有方法,当方法返回一个Left(错误)时,然后停止方法调用并返回在调用链中找到的第一个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)
            }
          }
        }
      }
    }
  }
Run Code Online (Sandbox Code Playgroud)

但它不起作用.无论如何,我真的不喜欢它所需的缩进.此外我想将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")
Run Code Online (Sandbox Code Playgroud)

是不是可以用Scala做这样的事情?也许同时使用Either和Option?

一个限制也是如果第一次调用失败,则不应该进行其他调用.我不想要一个解决方案,我称之为一切,然后加入eithers.

谢谢!

Deb*_*ski 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
Run Code Online (Sandbox Code Playgroud)

这至少摆脱了所有的筑巢.由于Scala Either默认情况下没有右偏,因此您必须手动指定相当多次,这会略微降低可读性.

  • 根据[2.12版本的文档:`两者都是右偏的,这意味着Right被假定为默认情况。如果为Left,则诸如map,flatMap等操作将返回Left值不变。](http://www.scala-lang.org/api/2.12.0/scala/util/Either.html)。有关此更改的更多详细信息,请参见[Scala 2.12官方概述](http://scala-lang.org/news/2.12.0#two-is-now-right-biased)。 (2认同)

Pet*_*lák 6

您已经使用的RightProjection允许您使用其flatMap方法完成您所需的操作.

(按照惯例,计算结果存储在Right计算失败的错误值中Left.但是没有其他原因,你也可以这样做LeftProjection.)

实际上,我们在这里RightProjection形成一个monad.您可以使用将值x转换为投影Right(x).right.如果你有一个投影p,你可以申请一个可能失败的计算fp调用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);
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

输出是:

-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)
Run Code Online (Sandbox Code Playgroud)