2012-08-24 8 views
8

"メソッドコールチェーン"を作成することができますか?すべてのメソッドが同じ[エラー、結果]のいずれかを返すかどうかを知りたいのですが。メソッドをチェーンで呼び出す

私がしたいのは、すべてのメソッドを連続して呼び出し、メソッドがLeft(Error)を返したときにメソッド呼び出しを停止し、コールチェーン内の最初のLeftを返します。

私はfold、map、projectionsを使っていくつかのものを試しました...しかし、私は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) 
      } 
      } 
     } 
     } 
    } 
    } 

をしかし、それは動作しません。 とにかく私は本当にそれがかかるインデントを好きではありません。

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の両方を使用していますか?

1つの制約は、最初の呼び出しが失敗した場合は、他の呼び出しが行われないようにすることです。私はすべてを呼び出し、どちらかに参加するソリューションは欲しくない。

ありがとうございます!

+0

こんにちは、形質検証することは、これはどちらかのモナドの説明である – fp4me

+1

探しているものです。 –

答えて

10

は(主に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

右寄せに+1して、デフォルトでどちらかの右のバイアスについてスカラーのユーザ(またはスカラー議論)のスレッドを見ました。 Scalazが最新の&最大で調理したものと一緒に行ってください。 – virtualeyes

+1

Scalazは '\ /'を得るつもりです(それはタイプミスではありません)。右バイアスされた代替案であるはずです。 – Debilski

+0

ありがとうございます、これは私が探しているものだと思われる –

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は必要に応じてロギングを追加します。

+0

ありがとう、私はあなたのコードのすべてを理解していない:)を確認します。 Btw私はコールチェーンに適切なエラーメッセージを返すことができなかったので、ロギングは必要ありません。誰もが指摘しているように、 –

+0

が必要です。計算。これはそれを行う一つの方法です。クエリの暗黙的なセッションのすべてのものは、ScalaQuery、BTWです。この例では、暗黙的なRightBiasedEitherクラスは必要ありませんが、左のエラー条件をエンコードする場合に便利です。「val result = for {x < - eitherResult as json}」 – virtualeyes

5

すでに使用しているRightProjectionは、flatMapメソッドを使用して必要なものを正確に行うことができます。

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

実は、私たちがここに持っているものですそのRightProjectionはモナドを形成する。 Right(x).rightを使用して、値xを投影に変換できます。また、投影がpの場合は、を呼び出して、pにおそらく失敗している計算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); 
    } 
    } 
} 

、出力は次のとおりです:完全な例を与えるためにscalazから

-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) 
+0

ありがとう、素晴らしい答え! –

関連する問題