withSpout在spark中是用来做DAG可视化的,它在代码里的用法如下(以map为例,spark 2.0.0版本)
def map[U: ClassTag](f: T => U): RDD[U] = withScope {
val cleanF = sc.clean(f)
new MapPartitionsRDD[U, T](this, (context, pid, iter) => iter.map(cleanF))
}
因为对scala语法比较生疏,初次见面,一脸懵逼,这里的withScope是个什么用法?乍看一下有种java的implements Comparable的感觉。
其实,withScope是一个函数。
map()的函数体其实就是调用了一下withScope,将泛型什么的先去掉,代码简单来看就是下面这个样子。
def map(f): RDD = withScope(func)
因为函数体只有简单的一句,所以省略了大括号 "{ }"。
map函数补上大括号可以是这个样子。
def map[U: ClassTag](f: T => U): RDD[U] = {
withScope {
val cleanF = sc.clean(f)
new MapPartitionsRDD[U, T](this, (context, pid, iter) => iter.map(cleanF))
}
}
函数只有单一参数,调用时,有时小括号和花括号是可以互换的 比如
rdd.map(x => x._1) //小括号
rdd.map{x => x._1} //花括号
这里,map()的参数是一个匿名函数,一句简短代码即可搞定。但是,当函数用一句搞不定时,就需要使用"{}"来界定代码块了。其实,于小括号和花括号之间来回切换的例子,在写spark程序时,经常遇到。
比如:
rdd.map(x => x._1).filter {
... //一些用一句代码不易搞定的复杂过滤逻辑
}
回到withScope,它就是一个只有单一参数的函数,而这个单一参数本身也是个函数。
private[spark] def withScope[U](body: => U): U = RDDOperationScope.withScope[U](sc)(body)
withScope的参数名是body,其类型是 => U。这表示body是一个函数,这个函数没有参数(=>左边没东西),而返回类型任意(=>右边是个泛型U)。
withScope的形式跟map类似。
柯里化函数
我们看到上文中的withScope()又调用了RDDOperationScope中定义的withScope,而且调用方式有些奇怪。有两个参数sc和body,而且用了两个括号。
再去看RDDOperationScope中withScope的定义,参数中也用了两个括号。第一个括号定义了sc和allowNesting参数,第二个括号定义了body参数。
这种形式在scala中叫做柯里化(currying)。
rivate[spark] def withScope[T](
sc: SparkContext,
allowNesting: Boolean = false)(body: => T): T = {
...
}
柯里化是将原先一次性接受的参数,改成了链式接受的形式。这里引用《快学scala》中的例子说明。
def mul(x: Int)(y: Int) = x * y //定义柯里化函数
mul(6)(7) //调用柯里化函数
严格来讲,首先调用mul(6),返回的结果是函数(y: Int) => 6 * y (x被替换成了6)。而这个函数又被应用到了7,最终得到42。
柯里化的本质是什么呢?
其实,上面的mul()是如下形式的简写。mul()本质上是定义了一个只有参数x的函数,其返回结果是另一个函数。
def mul(x: Int) = (y: Int) => x * y