假设你有很多方法:
def foo() : Try[Seq[String]]
def bar(s:String) : Try[String]
你要做一个for-comprhension:
for {
list <- foo
item <- list
result <- bar(item)
} yield result
当然这不会编译,因为在这种情况下,Seq不能与Try一起使用。
任何人都有一个很好的解决方案,如何写这个干净而不将它分成两个单独的?
我在三分之一的时间里遇到过这种语法问题,并认为现在是时候问这个了。
答案 0 :(得分:4)
您可以利用以下事实: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
,事情就会变得更加繁琐,因为没有自然的方法来展平Try[Seq[Try[String]]]
。 @Yury的答案证明了你必须要做的事情。
或者,如果您只对代码的副作用感兴趣,可以这样做:
for {
list <- foo
item <- list
result <- bar(item)
} result
这是有效的,因为foreach
的类型签名限制较少。
答案 1 :(得分:4)
恕我直言:尝试, Seq 超过了定义monad变换器所需的内容:
图书馆代码:
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 :(得分:2)
可以将Try转换为Option,您可以在for-comprehension中使用它。 E.g。
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
我第一次进入&#34; w&#34;,然后第二次进入1234。