8

すべてのメソッドが同じEither[Error、Result]を返す、ある種の「メソッド呼び出しチェーン」を作成できるかどうかを知りたいのですが。

私がやりたいのは、すべてのメソッドを連続して呼び出し、メソッドが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)
            }
          }
        }
      }
    }
  }

しかし、それは機能しません。そしてとにかく、私はそれが取るインデントが本当に好きではありません。さらに、エラーを問題を説明する新しい文字列に変換したいと思います(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の両方を使用していますか?

また、最初の呼び出しが失敗した場合、他の呼び出しを行わないようにするという制約もあります。すべてを呼び出してからどちらかに参加するソリューションは必要ありません。

ありがとう!

4

3 に答える 3

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

少なくともすべての入れ子を取り除きます。ScalaEitherはデフォルトでは右バイアスされていないため、手動で何度も指定する必要があります。これにより、読みやすさが少し低下します。

于 2012-08-24T10:35:17.040 に答える
6

すでに使用しているRightProjectionflatMapを使用すると、そのメソッドを使用して必要なことを正確に実行できます。

(慣例により、計算結果はに格納されRight、失敗した計算のエラー値はに格納されますLeft。ただし、他の理由はありません。同じことを行うことができますLeftProjection。)

実際、ここにあるのはRightProjectionモナドを形成することです。xを使用して、値を射影に変換できますRight(x).right。また、射影がある場合は、を呼び出すことでp、失敗する可能性のある計算fを適用できます。このようにして、そのようないくつかのメソッドを連鎖させることができます。pp.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)
于 2012-08-24T12:20:27.603 に答える
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では、必要に応じてログを追加します。

于 2012-08-24T11:11:02.127 に答える