`
Eastsun
  • 浏览: 308761 次
  • 性别: Icon_minigender_1
  • 来自: 天津
社区版块
存档分类
最新评论

Scala2.8探秘之for表达式

阅读更多
  虽然Scala2.8还在持续跳票中,但目前Nightly Build版本的可用性已经很高了,其中Scala2.8中主要特性都已经实现。毫无疑问,Scala2.8的发布将会是Scala发展中的一个重大里程碑。在这个版本中,不仅包括了许多Scala社区期待已久的特性,如命名参数、类型特殊化等等,详细的信息可以参看http://eastsun.iteye.com/blog/373710;而且包含了一些对之前Scala中设计不合理的地方的改进,例如Scala中对String以及数组的处理,在之前的版本中Scala编译器在编译的时候对这两种类型进行了一些比较特殊(魔法)的处理,这样做虽然某些程度上使得这两种类型在Scala中更加易用,但同时也破坏了Scala中的一致性,并随之产生了一些离奇的问题。举个例子,在Scala2.7.7 final中我们可以看到如下结果:
//Scala2.7.7 final
scala> val array = Array("String Array")
array: Array[java.lang.String] = Array(String Array)

scala> println(array.toString())
Array(String Array)

scala> println(array)
[Ljava.lang.String;@139491b

scala> "WOW" == "WOW".reverse
res4: Boolean = false

scala> "abcdefg" map { _.toUpperCase }
res5: Seq[Char] = ArrayBufferRO(A, B, C, D, E, F, G)         //注意,得到的是个Seq[Char]而不是String

  显然这样的运行结果有违我们的直觉,即使是对Scala有一定了解的人也未必能马上明白其中的奥妙。
  在Scala2.8中,这些问题都得到了一定的解决——可能有些解决方式会让你觉得退步了,但是保持了Scala的一致性,并且消除了编译器在背后做的那些魔法,使事情变得简单——虽然这样破坏了Scala的向后兼容性,但我认为这样做事非常值得的:这为Scala成为一门成功的语言垫下了基础。下面我们看看在Scala 2.8.0.r20117-b20091213020323中上面代码的运行结果:
scala> val array = Array("String Array")
array: Array[java.lang.String] = Array(String Array)

scala> println(array.toString())
[Ljava.lang.String;@335053

scala> println(array)
[Ljava.lang.String;@335053

scala> "WOW" == "WOW".reverse
res2: Boolean = true

scala> "abcdefg" map { _.toUpperCase }
res3: String = ABCDEFG

  可以看到,运行结果就如我们所预想的那样——固然其中数组的toString结果变得丑陋了,和Java中一样丑陋,但保持一致了。
  OK,String与数组的讨论就此为止,以后如果有时间我再来详细解释一下其背后的故事;现在我们转向本文要讨论的东东:Scala中的for表达式。注意:在本文中使用了两种不同的Scala版本:Scala2.7.7final与2.8.0.r20117-b20091213020323进行对比。

1.Scala2.8之前的for表达式

  在Scala中,通常有以下几种使用方式:
   for (p <- e) e'
   for (p <- e if g) e'
   for (p <- e; p' <- e' ...) e''

以及相应的
   for (p <- e) yield e'
   for (p <- e if g) yield e'
   for (p <- e; p' <- e' ...) yield e''

其中p,p'为Scala中的Pattern;e,e',e''为表达式;g为Boolean表达式。
  根据《The Scala Language Specification Version 2.7》,上面的for表达式将在编译阶段展开为下面的形式(没有考虑p为比较复杂的Pattern时的情形):
   for (p <- e) e'                 => e.foreach { case p => e' }
   for (p <- e if g) e'            => for (p <- e.filter{ (x1,...,xn) => g }) e' => ..
   for (p <- e; p' <- e' ...) e''  => e.foreach{ case p => for (p' <- e' ...) e'' }

以及相应的
   for (p <- e) yield e'                => e.map { case p => e' }
   for (p <- e if g) yield e'           => for (p <- e.filter{ (x1,...,xn) => g }) yield e'
   for (p <- e; p' <- e' ...) yield e'' => e.flatmap { case p => for (p' <- e' ...) yield e'' }

  注意的是,这个转换发生在类型检查之前。也就是说,对map,filter,flatMap以及foreach这四个方法的方法签名没有任何其它限制,只需要满足展开后for语句的类型检查。通常情况下,对于一个具有类型参数A的类C——一般表示某种数据结构(集合)——下面的定义方式是比较自然的:
class C[A] {
    def map[B](f: A => B): C[B]
    def flatMap[B](f: A => C[B]): C[B]
    def filter(p: A => Boolean): C[A]
    def foreach(b: A => Unit): Unit
}

  相对Java1.5中的for语句,Scala的实现更加灵活,并且以一种轻量级的方式实现了List comprehension。举个例子,下面几行代码实现了求一个List的全排列:
scala> def perm[T](ls: List[T]): List[List[T]] = ls match {
     |     case Nil => List(Nil)
     |     case xs  => for(x <- xs;ys <- perm(xs-x)) yield x::ys
     | }
perm: [T](List[T])List[List[T]]

scala> perm(1::2::3::Nil)
res2: List[List[Int]] = List(List(1, 2, 3), List(1, 3, 2), List(2, 1, 3),
                             List(2, 3, 1), List(3, 1, 2), List(3, 2, 1))

  但是这个转换规则还不甚完美。当转换后的表达式含有filter方法的时候,会产生几个问题。
(a)性能问题
  以一个简单的问题为例:求1~999中所有偶数之和。
  下面是两段类似的解决代码:
    val set = 1 until 1000
    var sum = 0
    for(num <- set;if(num%2 == 0)) sum += num

或者把if语句移到括号外面:
    val set = 1 until 1000
    var sum = 0
    for(num <- set) if(num%2 == 0) sum += num

  这两段代码功能上应该是等价的,但是运行效率如何呢?下面首先写一个粗略的测试函数:
/**
  计算count次调用call所需的时间,单位:毫秒
*/
def time(call : => Unit,count: Int): Long = {
    var cnt = count
    val start = System.currentTimeMillis
    while(cnt > 0) {
        call
        cnt -= 1
    }
    System.currentTimeMillis - start
}

  先在Scala2.7.7final中将每段代码各运行十万次:
scala> val set = 1 until 1000
set: Range = Range(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...

scala> time({
     |     var sum = 0
     |     for(num <- set;if(num%2 == 0)) sum += num
     | },100000)
res3: Long = 47390

scala>

scala> time({
     |     var sum = 0
     |     for(num <- set)  if(num%2 == 0) sum += num
     | },100000)
res4: Long = 3344

scala>

  测试结果很出乎意料:两段类似的代码,性能竟相差一个数量级!
  之所以会有这么大的差异,根据上述的转换规则,第一段代码将会转换为下面的实际执行代码:
    val set = 1 until 1000
    var sum = 0
    set.filter{ num => num%2 == 0) }.foreach{ case num => sum += num }

  而第二段代码实际执行的是:
    val set = 1 until 1000
    var sum = 0
    set.foreach{ case num => if(num%2 == 0) sum += num }

  相对而言,第一段代码会调用filter方法,创建一个新的集合类,而这个集合类包含了1~999中所有的偶数。显然这个过程是比较昂贵的,也是不必要的。

(b)运行顺序
  以
for (p <- e if g) e'

为例,实际运行的代码为:
e.filter{ (x1,...,xn) => g }.foreach{ case p => e'}

  可以看到,虽然直观上if g与e'在遍历的时候应该是依次循环执行;但事实上转换后if g整体先于e'执行。当g与e'中同时包含一个变量v,并且在g中对变量v进行改动时,实际运行结果可能和我们所预想的不一致。可能问题描述的不是很清楚,下面引用fineqtbull同学在for语句中内嵌if语句的副作用一文中的代码作为例子来说明:实现compress方法,其功能是将一个list中连续相同的元素删减至一个。比如compress(List(1,1,2,3,3,1,1,4)) == List(1,2,3,1,4),下面是两段类似的实现代码,咋一看都没问题,但运行结果却不一样。
Welcome to Scala version 2.7.7.final (Java HotSpot(TM) Client VM, Java 1.6.0_17).

scala> def compress1[T](ls: List[T]): List[T] = {
     |     var res = List(ls.first)
     |     for(x <- ls) if(x != res.last) res = res:::List(x)
     |     res
     | }
compress1: [T](List[T])List[T]

scala> def compress2[T](ls: List[T]): List[T] = {
     |     var res = List(ls.first)
     |     for(x <- ls;if(x != res.last)) res = res:::List(x)
     |     res
     | }
compress2: [T](List[T])List[T]

scala> compress1(List(1,1,2,3,3,1,1,4))
res0: List[Int] = List(1, 2, 3, 1, 4)

scala> compress2(List(1,1,2,3,3,1,1,4))
res1: List[Int] = List(1, 2, 3, 3, 4)

scala>

  有了之前的说明,我们不难发现其原因所在。但这样的结果显然违反了C/Java中习惯用法,很容易让一个刚接触Scala的人产生困惑。

2.Scala2.8中的for表达式

  刚才已经提到了Scala2.8之前for表达式所存在的两个问题,那么在Scala2.8中这两个问题有没有得到解决呢?下面将之前的代码在2.8.0.r20117-b20091213020323重新运行一次试试:
Welcome to Scala version 2.8.0.r20117-b20091213020323 (Java HotSpot(TM) Client VM, Java 1.6.0_17).

scala> def time(call : => Unit,count: Int): Long = {
     |     var cnt = count
     |     val start = System.currentTimeMillis
     |     while(cnt > 0) {
     |         call
     |         cnt -= 1
     |     }
     |     System.currentTimeMillis - start
     | }
time: (call: => Unit,count: Int)Long

scala> val set = 1 until 1000
set: scala.collection.immutable.Range ...

scala> time({
     |     var sum = 0
     |     for(num <- set;if(num%2 == 0)) sum += num
     | },100000)
res0: Long = 6906

scala> time({
     |     var sum = 0
     |     for(num <- set)  if(num%2 == 0) sum += num
     | },100000)
res1: Long = 4312

scala> def compress1[T](ls: List[T]): List[T] = {
     |     var res = List(ls.first)
     |     for(x <- ls) if(x != res.last) res = res:::List(x)
     |     res
     | }
compress1: [T](ls: List[T])List[T]

scala> def compress2[T](ls: List[T]): List[T] = {
     |     var res = List(ls.first)
     |     for(x <- ls;if(x != res.last)) res = res:::List(x)
     |     res
     | }
compress2: [T](ls: List[T])List[T]

scala> compress1(List(1,1,2,3,3,1,1,4))
res2: List[Int] = List(1, 2, 3, 1, 4)

scala> compress2(List(1,1,2,3,3,1,1,4))
res3: List[Int] = List(1, 2, 3, 1, 4)

scala>

  可以看到Scala2.8中已经很好的解决了这两个问题。对于一门语言,要想对已经存在的问题进行改进是很困难的,因为首先这些改进要尽可能少的影响已经存在的旧有代码,另一方面不能带来新的问题。幸运的是,Martin想到了一个简单而优雅的方法成功做到了这些。下面简单的叙述一下Martin的解决方法,感兴趣的同学可以去看Martin的原文Rethinking filter
  首先,Martin引入了一个新的方法withFilter,这个方法与filter方法一样以一个条件函数A => Boolean作为参数。与filter方法不同的是,withFilter方法是lazy的。它并不会创建一个新的包含符合条件的元素所组成的集合类,而是返回一个代理类WithFilter,这个类具有foreach、map、flatMap以及withFilter等方法,并且所有这些方法的调用是与原来条件组合的结果。下面是TraversableLike中WithFilter的实现,Scala2.8中所有的集合类都继承了TraversableLike:
  class WithFilter(p: A => Boolean) {

    /** Builds a new collection by applying a function to all elements of the
     *  outer $coll containing this `WithFilter` instance that satisfy predicate `p`.
     *
     *  @param f      the function to apply to each element.
     *  @tparam B     the element type of the returned collection.
     *  @tparam That  $thatinfo
     *  @param bf     $bfinfo
     *  @return       a new collection of type `That` resulting from applying the given function
     *                `f` to each element of the outer $coll that satisfies predicate `p`
     *                and collecting the results.
     *
     *  @usecase def map[B](f: A => B): $Coll[B] 
     *  
     *  @return       a new $coll resulting from applying the given function
     *                `f` to each element of the outer $coll that satisfies predicate `p`
     *                and collecting the results.
     */
    def map[B, That](f: A => B)(implicit bf: CanBuildFrom[Repr, B, That]): That = {
      val b = bf(repr)
      for (x <- self) 
        if (p(x)) b += f(x)
      b.result
    }

    /** Builds a new collection by applying a function to all elements of the
     *  outer $coll containing this `WithFilter` instance that satisfy predicate `p` and concatenating the results. 
     *
     *  @param f      the function to apply to each element.
     *  @tparam B     the element type of the returned collection.
     *  @tparam That  $thatinfo
     *  @param bf     $bfinfo
     *  @return       a new collection of type `That` resulting from applying the given collection-valued function
     *                `f` to each element of the outer $coll that satisfies predicate `p` and concatenating the results.
     *
     *  @usecase def flatMap[B](f: A => Traversable[B]): $Coll[B]
     * 
     *  @return       a new $coll resulting from applying the given collection-valued function
     *                `f` to each element of the outer $coll that satisfies predicate `p` and concatenating the results.
     */
    def flatMap[B, That](f: A => Traversable[B])(implicit bf: CanBuildFrom[Repr, B, That]): That = {
      val b = bf(repr)
      for (x <- self) 
        if (p(x)) b ++= f(x)
      b.result
    }

    /** Applies a function `f` to all elements of the outer $coll containing this `WithFilter` instance
     *  that satisfy predicate `p`.
     *
     *  @param  f   the function that is applied for its side-effect to every element.
     *              The result of function `f` is discarded.
     *              
     *  @tparam  U  the type parameter describing the result of function `f`. 
     *              This result will always be ignored. Typically `U` is `Unit`,
     *              but this is not necessary.
     *
     *  @usecase def foreach(f: A => Unit): Unit
     */   
    def foreach[U](f: A => U): Unit = 
      for (x <- self) 
        if (p(x)) f(x)

    /** Further refines the filter for this $coll.
     *
     *  @param q   the predicate used to test elements.
     *  @return    an object of class `WithFilter`, which supports
     *             `map`, `flatMap`, `foreach`, and `withFilter` operations.
     *             All these operations apply to those elements of this $coll which
     *             satify the predicate `q` in addition to the predicate `p`.
     */
    def withFilter(q: A => Boolean): WithFilter = 
      new WithFilter(x => p(x) && q(x))
  }
}

  在Scala2.8中,for表达式的转换方式大体保持不变,只是将以前使用filter的地方全部替换为withFilter方法。

结语:可以看到Scala2.8成功解决了之前Scala中存在的一些问题,使得Scala语言变得更加优雅、强大。你还在等待Java7的闭包吗?赶紧去尝试Scala2.8吧^_^

分享到:
评论
9 楼 regular 2010-03-17  
很动心。恨自己没时间做研究。
8 楼 lonlyleo 2009-12-29  
学习一下思想吧,能在实践中用这个吗?会不会被团队PK死?
7 楼 徐风子 2009-12-28  
你还在等待Java7的闭包吗?赶紧去尝试Scala2.8吧 ^_^
6 楼 nethibernate 2009-12-28  
正在学习scala,每看到里面的一种特性都会让我激动不已。我觉得这已经超出了一门语言的范畴了,它是所有开发思想的一种结合
5 楼 NumbCoder 2009-12-27  
发这么多代码 不容易啊~
听说Twitter都改用scala开发了
4 楼 helian 2009-12-27  
好久没关注scala了,LZ有没有什么基于scala的项目值得推荐?
3 楼 donyee 2009-12-27  
刚改了一些groovy代码到scala,学习函数式语言看看
期待lz更多的关于scala的介绍
2 楼 chinpom 2009-12-27  
我对Scala非常看好,不管它以后会不会流行,都很值得学习,因为它已经是我心中下一代编程语言的典范了。
1 楼 ytl 2009-12-27  
好文章

相关推荐

    scala2.8 api

    scala 2.8 api 文档 chm格式

    scala正则表达式与模式匹配.doc

    MatchIterator 是一种特殊的 Scala 集合迭代器,可以使用 for 循环迭代输出所有匹配的模式。 2. `findAllMatchIn(source: CharSequence)`:该方法返回所有匹配的模式。 3. `findFirstIn(source: CharSequence)`:该...

    scala-collections-charts:Scala 2.8 Collections API主要类型为点图(GraphViz)

    Scala 2.8 Collection API图表 这是 .dot文件的一个小集合,其中包含与Scala 2.8 Collection API最相关类型的图表说明。 您可以在上看到最终结果。 这些图表也可以在上。 任何反馈当然都是非常欢迎的。 如何建造 在...

    cron4s:Scala的跨平台CRON表达式解析

    cron4s是一个专门为Scala设计的库,它提供了一个强大的、跨平台的解决方案,用于解析和操作CRON(Cron)表达式。CRON是Unix/Linux系统中广泛使用的定时任务调度工具,通过特定的语法来定义周期性的任务。cron4s库...

    scala-2.8.0.final(1)

    scala-2.8.0.final scala-2.8.0.final

    Scala-IDE(for Eclipse Juno Scala 2.9)

    总的来说,Scala IDE for Eclipse Juno Scala 2.9是为Scala开发者量身定制的一个强大工具,它结合了Eclipse的优秀特性与Scala语言的独特优势,使得开发过程更为顺畅。无论是初学者还是经验丰富的程序员,都能从中...

    Java8与Scala中的Lambda表达式深入讲解

    Java8与Scala中的Lambda表达式深入讲解 Lambda表达式是函数式编程的基本组成部分,许多现代编程语言都把它作为关键部分集成在语言中。Java8终于要支持Lambda表达式!在这篇文章中,我们将深入讲解Java8与Scala中的...

    scala-2.11.8.rar

    5. **模式匹配和类型系统**:Scala的模式匹配和强大的类型系统是其独特之处。源码中可以看到这些特性的底层实现。 6. **Scala.js**:虽然在这个特定的压缩包中可能没有,但Scala 2.11.x版本支持Scala.js,可以将...

    Scala and Spark for Big Data Analytics

    Scala and Spark for Big Data Analytics by Md. Rezaul Karim English | 25 July 2017 | ISBN: 1785280848 | ASIN: B072J4L8FQ | 898 Pages | AZW3 | 20.56 MB Harness the power of Scala to program Spark and ...

    Programming-in-Scala-2nd.pdf

    advantage of the latest features in Scala 2.8, and to using this book as the definitive reference for it, direct from the creator of the language I’ve grown to depend on. Alex Payne Portland, Oregon ...

    Scala for Machine Learning

    Scala是一种强大的、多范式的编程语言,尤其在大数据处理和机器学习领域,它已经逐渐成为首选工具之一。这个标题暗示了本书将深入探讨如何利用Scala的特性来构建高效、可靠的机器学习系统。以下是基于该主题的一些...

    Learning Scala Practical Functional Programming for the JVM

    2. **表达式思维与编写**:Scala语法的一大特点在于其表达式导向,这意味着大部分编程结构都可以视为表达式,这为编写简洁、可读性强的代码提供了可能。作者会引导读者如何以表达式的方式思考问题并进行编程。 3. *...

    reb4s:Scala 的正则表达式生成器

    关于 reb4s reb4s的目的是围绕 JRE 的类提供的正则表达式语法提供一个纯 Scala 包装器。 它是一个分支,它通过纯 Java API 提供相同的功能。 与直接编写正则表达式相比,reb4s具有以下优点: reb4s API 保证正确的...

    scala-2.12.1 for Linux

    标题“scala-2.12.1 for Linux”表明我们讨论的是Scala的特定版本2.12.1,这个版本是为Linux操作系统设计的。Scala的版本号通常遵循主版本号.次要版本号.修订版本号的格式,这里的2.12.1意味着这是一个稳定版本,...

    xalanjava源码-SyntaxDiagramGenerator:Scala2.8的语法图生成插件,基于http://blog.32lea

    xalan java源码基于以下位置的示例代码和示例: 根据原始知识共享署名-相同方式共享 3.0 未移植许可证获得许可。 在 SBT 中,运行package来构建。...project/boot/scala-2.8.0/lib/scala-library.j

    scala for data science

    ### Scala for Data Science #### 一、概述 《Scala for Data Science》是一本专注于使用Scala进行数据科学项目开发的专业书籍。本书由Pascal Bugnion撰写,并于2016年由Packt Publishing出版。该书旨在帮助读者...

    Scala for the Impatient

    本书《Scala for the Impatient》是为那些对快速学习Scala编程感兴趣但又不愿意阅读长篇大论的读者所写的。作者Cay S. Horstmann假设读者已经有了Java、C#或C++等语言的编程背景,因此不会在变量、循环或类等基础...

    Scala中正则表达式以及与模式匹配结合(多种方式)

    在Scala中,正则表达式是处理文本模式匹配的强大工具,它能够识别和操作字符串中的复杂模式。Scala的模式匹配提供了一种灵活的方式来对数据进行解构和分类,当与正则表达式结合时,这种能力得到了极大的提升。 首先...

Global site tag (gtag.js) - Google Analytics