scala语法在spark withScope上的应用

withSpout在spark中是用来做DAG可视化的,它在代码里的用法如下(以map为例,spark 1.5.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(body)

因为函数体只有简单的一句,所以省略了大括号 "{ }"。
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是一个传名参数。

传名参数

传名参数 仅在被使用时触发实际参数的求值运算。 它们与 传值参数 正好相反。
传名参数的优点是,如果它们在函数体中未被使用,则不会对它们进行求值。 另一方面,传值参数的优点是它们仅被计算一次。
传名参数给人的感觉就像是字符串替换,最终把body替换成用户写的代码。
跟踪withScop的代码可以看到body的使用。


body的调用

最开始理解有误,以为body的类型是个无参函数。无参函数作为参数的话,可以参考下面的代码中的print2()。

object TestMain {

  def main(args: Array[String]): Unit = {
    print1(getInt)
    println("-----------")
    print2(getInt)
  }

  def print1(f: => Int): Unit = { //传名参数
    println(f)
    println(f.getClass)
  }

  def print2(f: () => Int): Unit = { //函数参数
    println(f)
    println(f.getClass)
  }

  def getInt(): Int = {
    1
  }
}

该代码运行结果如下

1
int
-----------
<function0>
class com.iflytek.gnome.data.tmpsupport.main.TestMain$$anonfun$main$2

柯里化函数

我们看到上文中的withScope()又调用了RDDOperationScope中定义的withScope,而且调用方式有些奇怪。有两个参数sc和body,而且用了两个括号。
再去看RDDOperationScope中withScope的定义,参数中也用了两个括号。第一个括号定义了sc和allowNesting参数,第二个括号定义了body参数。
这种形式在scala中叫做柯里化(currying)。

private[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

如《快学scala》所说

如你所见,多参数不过是个虚饰,并不是编程语言的什么根本性的特质。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容