12

それらをテストするためのケースクラスと関数がほとんどないとします。

case class PersonName(...)
case class Address(...)
case class Phone(...)

def testPersonName(pn: PersonName): Either[String, PersonName] = ...
def testAddress(a: Address): Either[String, Address] = ...
def testPhone(p: Phone): Either[String, Phone] = ...

ここで、新しいケース クラスPersonとテスト関数を定義します。これは高速で失敗します

case class Person(name: PersonName, address: Address, phone: Phone)

def testPerson(person: Person): Either[String, Person] = for {
  pn <- testPersonName(person.name).right
  a <- testAddress(person.address).right
  p <- testPhone(person.phone).right
} yield person;

ここで、高速に失敗するのではなく、関数にエラーtestPerson蓄積させたいと考えています。

これらすべての関数testPersonを常に実行して戻りたいと思います。どうやってやるの ?test*Either[List[String], Person]

4

4 に答える 4

17

test*メソッドを分離して、内包表記の使用をやめたい!

(何らかの理由で) scalaz が選択肢にないと仮定すると... 依存関係を追加しなくても実行できます。

多くの scalaz の例とは異なり、これはライブラリが "通常の" scala よりも冗長性を大幅に削減していない例です:

def testPerson(person: Person): Either[List[String], Person] = {
  val name  = testPersonName(person.name)
  val addr  = testAddress(person.address)
  val phone = testPhone(person.phone)

  val errors = List(name, addr, phone) collect { case Left(err) => err }

  if(errors.isEmpty) Right(person) else Left(errors)      
}
于 2014-01-25T14:21:48.267 に答える
14

Scala の内包表記 (とforの呼び出しの組み合わせを desugar する) は、後続のステップで以前の計算の結果にアクセスできるように、モナド計算を順序付けできるように設計されています。次の点を考慮してください。flatMapmap

def parseInt(s: String) = try Right(s.toInt) catch {
  case _: Throwable => Left("Not an integer!")
}

def checkNonzero(i: Int) = if (i == 0) Left("Zero!") else Right(i)

def inverse(s: String): Either[String, Double] = for {
  i <- parseInt(s).right
  v <- checkNonzero(i).right
} yield 1.0 / v

これによりエラーが蓄積されることはありません。実際、それができる合理的な方法はありません。を呼び出すとしますinverse("foo")。次にparseIntは明らかに失敗します。つまり、 の値を取得する方法がありません。つまり、シーケンスのステップにi進む方法がないことを意味します。checkNonzero(i)

あなたの場合、計算にはこの種の依存関係はありませんが、使用している抽象化 (モナディック シーケンス) はそれを認識していません。あなたが望むのは、Eitherモナドではないが、それはapplicativeのようなタイプです。違いの詳細については、こちらの回答を参照してください。

たとえば、個々の検証方法を変更せずに、 Scalazを使用して次のように書くことができます。Validation

import scalaz._, syntax.apply._, syntax.std.either._

def testPerson(person: Person): Either[List[String], Person] = (
  testPersonName(person.name).validation.toValidationNel |@|
  testAddress(person.address).validation.toValidationNel |@|
  testPhone(person.phone).validation.toValidationNel
)(Person).leftMap(_.list).toEither

もちろん、これは必要以上に冗長であり、いくつかの情報を捨てていますが、Validation全体を使用すると少しきれいになります。

于 2014-01-25T14:33:53.790 に答える
4

@TravisBrownが言っているように、理解はエラーの蓄積と実際には混ざりません。実際、細かいエラー制御が必要ない場合に一般的に使用します。

理解のための A は、最初に見つかったエラーでそれ自体を「短絡」させます。これは、ほとんどの場合、あなたが望むものです。

あなたがやっている悪いことはString、例外のフロー制御を行うために使用しています。および を使用して常にEither[Exception, Whatever]ロギングを使用し、微調整する必要がscala.util.control.NoStackTraceありscala.util.NonFatalます。

具体的には、はるかに優れた代替手段があります。

scalaz.EitherTscalaz.ValidationNel

更新:(これは不完全です。あなたが何を望んでいるのか正確にはわかりません)。や など、マッチングよりも優れたオプションがありgetOrElseますrecover

def testPerson(person: Person): Person = {
  val attempt = Try {
    val pn = testPersonName(person.name)
    val a = testAddress(person.address)
    testPhone(person.phone)
  }
  attempt match {
    case Success(person) => //..
    case Failure(exception) => //..
  }
}
于 2014-01-25T14:22:01.300 に答える