Scala编程指南 揭示Scala的本质

2012-11-12

  Scala 是一种基于JVM,集合了面向对象编程和函数式编程优点的高级程序设计语言。在《Scala编程指南 更少的字更多的事》中我们从几个方面见识了Scala 简洁,可伸缩,高效的语法。我们也描述了许多Scala 的特性。本文为《Programming Scala》第三章,我们会在深入Scala 对面向对象编程和函数式编程的支持前,完成对Scala 本质的讲解。

  Scala 本质

  在我们深入Scala 对面向对象编程以及函数式编程的支持之前,让我们来先完成将来可能在程序中用到的一些Scala 本质和特性的讨论。

  操作符?操作符?

  Scala 一个重要的基础概念就是所有的操作符实际上都是方法。考虑下面这个最基础的例子。

  // code-examples/Rounding/one-plus-two-script.scala  1 + 2 两个数字之间的加号是个什么呢?是一个方法。第一,Scala 允许非字符型方法名称。你可以把你的方法命名为+,-,$ 或者其它任何你想要的名字(译着:后面会提到例外)。第二,这个表达式等同于1 .+(2)。(我们在1 的后面加了一个空格因为1. 会被解释为Double 类型。)当一个方法只有一个参数的时候,Scala 允许你不写点号和括号,所以方法调用看起来就像是操作符调用。这被称为“中缀表示法”,也就是操作符是在实例和参数之间的。我们会很快见到很多这样的例子。

  类似的,一个没有参数的方法也可以不用点号,直接调用。这被称为“后缀表示法”。

  Ruby 和SmallTalk 程序员现在应该感觉和在家一样亲切了。因为那些语言的使用者知道,这些简单的规则有着广大的好处,它可以让你用自然的,优雅的方式来创建应用程序。

  那么,哪些字符可以被用在标识符里呢?这里有一个标识符规则的概括,它应用于方法,类型和变量等的名称。要获取更精确的细节描述,参见[ScalaSpec2009]。Scala 允许所有可打印的ASCII 字符,比如字母,数字,下划线,和美元符号$,除了括号类的字符,比如‘(’, ‘)’, ‘[’, ‘]’, ‘{’, ‘}’,和分隔类字符比如‘`’, ‘’’, ‘'’, ‘"’, ‘.’, ‘;’, 和 ‘,’。除了上面的列表,Scala 还允许其他在u0020 到u007F 之间的字符,比如数学符号和“其它” 符号。这些余下的字符被称为操作符字符,包括了‘/’, ‘<’ 等。

  1 不能使用保留字

  正如大多数语言一样,你不能是用保留字作为标识符。我们在《第2章 - 打更少的字,做更多的事》的“保留字” 章节列出了所有的保留字。回忆一下,其中有些保留字是操作符和标点的组合。比如说,简单的一个下划线(‘_’) 是一个保留字!

  2 普通标识符 - 字母,数字以及 ‘$’, ‘_’ , 操作符的组合

  和Java 以及很多其它语言一样,一个普通标志符可以以一个字母或者下划线开头,紧跟着更多的字母,数字,下划线和美元符号。和Unicode 等同的字符也是被允许的。然而,和Java 一样,Scala 保留了美元符号作为内部使用,所以你不应该在你自己的标识符里使用它。在一个下划线之后,你可以接上字母,数字,或者一个序列的操作符字符。下划线很重要,它告诉编译器把后面直到空格之前所有的字符都处理为标识符。比如,val xyz__++ = 1 把值1 赋值给变量xyz__++,而表达式val xyz++= = 1却不能通过编译,因为这个标识符同样可以被解释为xyz ++=,看起来像是要把某些东西加到xyz 后面去。类似的,如果你在下划线后接有操作符字符,你不能把它们和字母数字混合在一起。这个约束避免了像这样的表达式的二义性:abc_=123。这是一个标识符abc_=123 还是给abc_ 赋值123 呢?

  3 普通标识符 - 操作符

  如果一个标识符以操作符为开头,那么余下的所有字符都必须是操作符字符。

  4 反引用字面值

  一个标识符可以是两个反单引号内一个任意的字符串(受制于平台的限制)。比如val `this is a valid identifier` = "Hello World!"。回忆一下我们可以发现,这个语法也是引用Java 或者.NET 的类库中和Scala 保留字的名称一样的方法时候所用的方式,比如java.net.Proxy.`type`()。

  5 模式匹配标识符

  在模式匹配表达式中,以小写字母开头的标识都会被解析为变量标识符,而以大写字母开头的标识会被解析为常量标识符。这个限定避免了一些由于非常简洁的变量语法而带来的二义性,例如:不用写val 关键字。

  语法糖蜜

  一旦你知道所有的操作符都是方法,那么理解一些不熟悉的Scala 代码就会变的相对容易些了。你不用担心那些充满了新奇操作符的特殊案例。在《第1章 - 从0 分到60 分:Scala 介绍》中的“初尝并发” 章节中,我们使用了Actor 类,你会注意到我们使用了一个惊叹号(!)来发送消息给一个Actor。现在你知道!只是另外一个方法而已,就像其它你可以用来和Actor 交互的快捷操作符一样。类似的,Scala 的XML 库提供了 操作符来渗入到文档结构中去。这些只是scala.xml.NodeSeq 类的方法而已。

  灵活的方法命名规则能让你写出就像Scala 原生扩展一样的库。你可以写一个数学类库,处理数字类型,加减乘除以及其它常见的数学操作。你也可以写一个新的行为类似Actors 的并发消息层。各种的可能性仅受到Scala 方法命名限制的约束。

  警告

  别因为你可以就觉得你应该这么作。当用Scala 来设计你自己的库和API 的时候,记住,晦涩的标点和操作符会难以被程序员所记住。过量使用这些操作符会导致你的代码充满难懂的噪声。坚持已有的约定,当一个快捷符号没有在你脑海中成型的时候,清晰地把它拼出来吧。

  不用点号和括号的方法

  为了促进阅读性更加的编程风格,Scala 在方法的括号使用上可谓是灵活至极。如果一个方法不用接受参数,你可以无需括号就定义它。调用者也必须不加括号地调用它。如果你加上了空括号,那么调用者可以有选择地加或者不加括号。例如,List 的size 方法没有括号,所以你必须写List(1,2,3).size。如果你尝试写List(1,2,3).size() 就会得到一个错误。然而,String 类的length 方法在定义时带有括号,所以,"hello".length() 和"hello".length 都可以通过编译。

  Scala 社区的约定是,在没有副作用的前提下,省略调用方法时候的空括号。所以,查询一个序列的大小(size)的时候可以不用括号,但是定义一个方法来转换序列的元素则应该写上括号。这个约定给你的代码使用者发出了一个有潜在的巧妙方法的信号。

  当调用一个没有参数的方法,或者只有一个参数的方法的时候,还可以省略点号。知道了这一点,我们的List(1,2,3).size 例子就可以写成这样:

  // code-examples/Rounding/no-dot-script.scala  List(1, 2, 3) size 很整洁,但是又令人疑惑。在什么时候这样的语法灵活性会变得有用呢?是当我们把方法调用链接成自表达性的,自我解释的语“句” 的时候:

  // code-examples/Rounding/no-dot-better-script.scala  def isEven(n: Int) = (n % 2) == 0  List(1, 2, 3, 4) filter isEven foreach println 就像你所猜想的,运行上面的代码会产生如下输出:

  24 Scala 这种对于方法的括号和点号不拘泥的方式为书写域特定语言(Domain-Specific Language)定了基石。我们会在简短地讨论一下操作符优先级之后再来学习它。

  优先级规则

  那么,如果这样一个表达式:2.0 * 4.0 / 3.0 * 5.0 实际上是Double  上的一系列方法调用,那么这些操作符的调用优先级规则是什么呢?这里从低到高表述了它们的优先级[ScalaSpec2009]。

  ◆所有字母

  ◆|

  ◆^

  ◆&

  ◆< >

  ◆= !

  ◆:

  ◆+ -

  ◆* / %

  ◆所有其它特殊字符

  在同一行的字符拥有同样的优先级。一个例外是当= 作为赋值存在时,它拥有最低的优先级。

  因为* 和/ 有一样的优先级,下面两行scala 对话的行为是一样的。

  scala> 2.0 * 4.0 / 3.0 * 5.0res2: Double = 13.333333333333332  scala> (((2.0 * 4.0) / 3.0) * 5.0)res3: Double = 13.333333333333332 在一个左结合的方法调用序列中,它们简单地进行从左到右的绑定。你说“左绑定”?在Scala 中,任何以冒号: 结尾的方法实际上是绑定在右边的,而其它方法则是绑定在左边。举例来说,你可以使用:: 方法(称为“cons”,“constructor” 构造器的缩写)在一个List 前插入一个元素。

  scala> val list = List('b', 'c', 'd')  list: List[Char] = List(b, c, d)  scala> 'a' :: list  res4: List[Char] = List(a, b, c, d) 第二个表达式等效于list.::(a)。在一个右结合的方法调用序列中,它们从右向左绑定。那左绑定和有绑定混合的表达式呢?

  scala> 'a' :: list ++ List('e', 'f')  res5: List[Char] = List(a, b, c, d, e, f) (++ 方法链接了两个list。)在这个例子里,list 被加入到List(e,f) 中,然后a 被插入到前面来创建最后的list。通常我们最好加上括号来消除可能的不确定因素。

  提示

  任何名字以: 结尾的方法都向右边绑定,而不是左边。

  最后,注意当你使用scala 命令的时候,无论是交互式还是使用脚本,看上去都好像可以在类型之外定义“全局”变量和方法。这其实是一个假象;解释器实际上把所有定义都包含在一个匿名的类型中,然后才去生成JVM 或者.NET CLR 字节码。

分享到:
0
相关阅读
友情链接
© 2018 我考网 http://www.woexam.com 中国互联网举报中心 湘ICP备18023104号 京公网安备 11010802020116号
违法和不良信息举报:9447029@qq.com