您所在的位置:开发 > Java > Java+ > Scala编程指南 揭示Scala的本质(1)

Scala编程指南 揭示Scala的本质(1)

2010-09-14 15:34 bbsmrdj译 51CTO 字号:T | T
一键收藏,随时查看,分享好友!

在上一章中我们从几个方面见识了Scala 简洁,可伸缩,高效的语法。我们也描述了许多Scala 的特性。本章,我们会在深入Scala 对面向对象编程和函数式编程的支持前,完成对Scala 本质的讲解。

AD:

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

51CTO推荐专题:Scala编程语言

Scala 本质

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

操作符?操作符?

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

  1. // code-examples/Rounding/one-plus-two-script.scala  
  2. 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 例子就可以写成这样:

  1. // code-examples/Rounding/no-dot-script.scala  
  2. List(1, 2, 3) size 

很整洁,但是又令人疑惑。在什么时候这样的语法灵活性会变得有用呢?是当我们把方法调用链接成自表达性的,自我解释的语“句” 的时候:

  1. // code-examples/Rounding/no-dot-better-script.scala  
  2. def isEven(n: Int) = (n % 2) == 0  
  3. List(1, 2, 3, 4) filter isEven foreach println 

就像你所猜想的,运行上面的代码会产生如下输出:

  1. 24 

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

优先级规则

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

◆所有字母

◆|

◆^

◆&

◆< >

◆= !

◆:

◆+ -

◆* / %

◆所有其它特殊字符

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

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

  1. scala> 2.0 * 4.0 / 3.0 * 5.0res2: Double = 13.333333333333332  
  2. scala> (((2.0 * 4.0) / 3.0) * 5.0)res3: Double = 13.333333333333332 

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

  1. scala> val list = List('b', 'c', 'd')  
  2. list: List[Char] = List(b, c, d)  
  3. scala> 'a' :: list  
  4. res4: List[Char] = List(a, b, c, d) 

第二个表达式等效于list.::(a)。在一个右结合的方法调用序列中,它们从右向左绑定。那左绑定和有绑定混合的表达式呢?

  1. scala> 'a' :: list ++ List('e', 'f')  
  2. res5: List[Char] = List(a, b, c, d, e, f) 

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

提示

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

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

内容导航
 第 1 页:Scala 本质  第 2 页:Scala领域特定语言
 第 3 页:Scala循环结构  第 4 页:模式匹配
 第 5 页:在Case 字句中绑定嵌套变量



分享到:

热点职位

更多>>

热点专题

更多>>

读书

Visual C# 2005从入门到精通
Microsoft Visual C#功能强大、使用简单。本书全面介绍了如何利用Visual Studio2005和NET Framework来进行C#编程。作者将C#的各

51CTO旗下网站

领先的IT技术网站 51CTO 领先的中文存储媒体 WatchStor 中国首个CIO网站 CIOage 中国首家数字医疗网站 HC3i