Используя для понимания, Try и последовательности в Scala
Скажем, у вас есть куча методов:
def foo() : Try[Seq[String]]
def bar(s:String) : Try[String]
и вы хотите сделать следующее:
for {
list <- foo
item <- list
result <- bar(item)
} yield result
конечно, это не скомпилируется, поскольку Seq не может использоваться с Try в этом контексте.
У кого-нибудь есть хорошее решение, как написать это чистое, не разбивая его на отдельные два для?
Я столкнулся с этой проблемой синтаксиса в течение трети времени и подумал, что настало время спросить об этом.
Ответы
Ответ 1
IMHO: Попробовать и Seq больше, чем вам нужно, чтобы определить трансформатор монады:
Код для библиотеки:
case class trySeq[R](run : Try[Seq[R]]) {
def map[B](f : R => B): trySeq[B] = trySeq(run map { _ map f })
def flatMap[B](f : R => trySeq[B]): trySeq[B] = trySeq {
run match {
case Success(s) => sequence(s map f map { _.run }).map { _.flatten }
case Failure(e) => Failure(e)
}
}
def sequence[R](seq : Seq[Try[R]]): Try[Seq[R]] = {
seq match {
case Success(h) :: tail =>
tail.foldLeft(Try(h :: Nil)) {
case (Success(acc), Success(elem)) => Success(elem :: acc)
case (e : Failure[R], _) => e
case (_, Failure(e)) => Failure(e)
}
case Failure(e) :: _ => Failure(e)
case Nil => Try { Nil }
}
}
}
object trySeq {
def withTry[R](run : Seq[R]): trySeq[R] = new trySeq(Try { run })
def withSeq[R](run : Try[R]): trySeq[R] = new trySeq(run map (_ :: Nil))
implicit def toTrySeqT[R](run : Try[Seq[R]]) = trySeq(run)
implicit def fromTrySeqT[R](trySeqT : trySeq[R]) = trySeqT.run
}
и после того, как вы сможете использовать for-comrehension (просто импортируйте свою библиотеку):
def foo : Try[Seq[String]] = Try { List("hello", "world") }
def bar(s : String) : Try[String] = Try { s + "! " }
val x = for {
item1 <- trySeq { foo }
item2 <- trySeq { foo }
result <- trySeq.withSeq { bar(item2) }
} yield item1 + result
println(x.run)
и работает для:
def foo() = Try { List("hello", throw new IllegalArgumentException()) }
// x = Failure(java.lang.IllegalArgumentException)
Ответ 2
Вы можете воспользоваться тем, что Try
можно преобразовать в Option
и Option
в Seq
:
for {
list <- foo.toOption.toSeq // toSeq needed here, as otherwise Option.flatMap will be used, rather than Seq.flatMap
item <- list
result <- bar(item).toOption // toSeq not needed here (but allowed), as it is implicitly converted
} yield result
Это вернет (возможно, пуст, если сбой Try
) Seq
.
Если вы хотите сохранить все детали исключения, вам понадобится Try[Seq[Try[String]]]
. Это невозможно сделать с помощью сингла для понимания, поэтому лучше всего придерживаться простого map
:
foo map {_ map bar}
Если вы хотите смешивать ваши Try
и Seq
по-другому, все становится странно, так как нет естественного способа сгладить a Try[Seq[Try[String]]]
. Ответ @Yury демонстрирует то, что вам нужно сделать.
Или, если вас интересуют только побочные эффекты вашего кода, вы можете просто:
for {
list <- foo
item <- list
result <- bar(item)
} result
Это работает, потому что foreach
имеет менее строгую подпись типа.
Ответ 3
Попытка может быть преобразована в Опцию, которую вы можете использовать, чтобы использовать ее для понимания. Например.
scala> def testIt() = {
| val dividend = Try(Console.readLine("Enter an Int that you'd like to divide:\n").toInt)
| dividend.toOption
| }
testIt: ()Option[Int]
scala> for (x <- testIt()) println (x * x)
Enter an Int that you'd like to divide:
scala> for (x <- testIt()) println (x * x)
Enter an Int that you'd like to divide:
1522756
Первый раз я ввел "w", затем второй раз 1234.