我是Scala/Spark World的新手。我试图弄清楚为什么可以接受此代码:
val artistID = rawArtistData.flatMap { line =>
val (id, name) = line.span(_ != 't')
if (name.isEmpty) {
None
} else {
try {
Some(id.toInt, name.trim)
} catch {
case e: NumberFormatException => None
}
}
}
这不是:
val artistID = rawArtistData.flatMap { line =>
val (id, name) = line.span(_ != 't')
if (name.isEmpty) {
None
} else {
try {
(id.toInt, name.trim)
} catch {
case e: NumberFormatException => None
}
}
}
我知道这与类型不匹配有关,但仅此而已。我的问题是为什么我不能退还元组?
要查看正在发生的事情,让我们清晰地说:
if (name.isEmpty) {
None // Option[Nothing]
} else {
try {
Some(id.toInt, name.trim) // Option[(Int, String)]
} catch {
case e: NumberFormatException => None // Option[Nothing]
}
}
没有什么是底部类型,而您尝试与Nothing
成功合并的任何东西。因此,None
和Some(...)
之间的统一总是有效的 - 使用List(None, Some(...))
尝试一下,这将始终导致List[Option[...]]
类型。
现在,如果您在那里有元组
try {
Some(id.toInt, name.trim) // (Int, String)
} catch {
case e: NumberFormatException => None // Option[Nothing]
}
编译器将尝试统一(Int, String)
和Option[Nothing]
...不能。好吧,这有点可以,因为Scala中任何两件事的超模型始终是Any
-但这对您无济于事。这就是为什么您会收到错误消息的原因,因为元组!=一个选项
flatMap
方法需要一个功能f
,具有以下签名:
(T) ⇒ TraversableOnce[U]
(_, _)
和Option[(_, _)]
都不是TraversableOnce
,但是后者的伴随对象具有隐式option2Iterable
方法:
def option2Iterable[A](xo: Option[A]): Iterable[A]
和Iterable[_]
是TraversableOnce[_]
。这就是为什么您可以返回Option
并且无法返回普通Tuple2[_, _]
。
实际上,您可以用Try
简化此内容:
import scala.util.Try
rawArtistData.flatMap { line => Try {
line.span(_ != 't') match {
case (id, name) => (id.toInt, name)
}
}.toOption }
这与Spark无关,而与Scala或功能编程有关。Flatmap操作使基础结构变平。例如,List[List[A]]
至List[A]
,或Option[Option[A]]
到Option[A]
。同样,RDD[List[A]]
到RDD[A]
,它将跳过RDD[None]
的行。
在此处使用Flatmap并不是真正的直观,您也可以首先进行地图,然后进行过滤器操作。通过使用两个操作,这看起来可能更昂贵,但是由于它们是单一的,Spark会优化引擎盖下的操作,尤其是使用新的钨引擎。