《深入理解Scala》——第1章,第1.3节静态类型和表达力

简介:

本节书摘来自异步社区《深入理解Scala》一书中的第1章,第1.3节静态类型和表达力,作者[美]Josh Suereth,更多章节内容可以访问云栖社区“异步社区”公众号查看

1.3 静态类型和表达力
深入理解Scala
开发人员中有一个误解,认为静态类型必然导致冗长的代码。之所以如此是因为很多继承自C的语言强制要求程序员必须在代码中多处明确地指定类型。随着软件开发技术和编译器理论的发展,情况已经改变。Scala利用了其中一些技术进步来减少样板(boilerplate)代码,保持代码简洁。
Scala做了以下几个简单的设计决策,以提高代码表达力。
• 把类型标注(type annotation)换到变量右边。
• 类型推断。
• 可扩展的语法。
• 用户自定义的隐式转换。
我们先看看Scala是怎么改变类型标注的位置的。

1.3.1 换边
Scala把类型标注放在变量的右侧。像Java或C++等几种静态类型语言,一般都必须声明变量、返回值和参数的类型。在指定变量或参数的类型时,延续自C的做法是把类型标注放在变量名的左边。对于方法的参数和返回值来说,这是可以接受的,但在构造不同风格(style)的变量时就容易产生混淆。C++是最好的例子,它有很丰富的变量修饰符选项,比如volatile、const、指针和引用等。


6060040627314d5c820af5e39b83308ffa2ee85f

把变量的类型和其修饰符混在一起的做法引致一些极其复杂的类型定义。而Scala,和其他几种语言,则把类型标注放在变量的右边。把变量类型和修饰符分开能帮助程序员在读代码时减少一些复杂性。


193c35827705e2d420600366ea6fdf77a85fbddf

上例演示了仅仅把类型标准搬到变量右边,我们就可以使代码简化不少,而这还不是全部,Scala能通过类型推断进一步减少语法噪音。

1.3.2 类型推断
只要能够进行类型推断,Scala就会执行类型推断,从而进一步降低语法噪音(syntactic noise)。类型推断是指编译器自行判断类型标注,而不是强迫用户去指定。用户当然可以提供类型标注(如果他想),但他也可以选择让编译器来干这活。


04d12189a3c76227b205f35550c5864dd3dd0a99

这个特性能够极大地减少在其他强类型的语言中常见的语法噪音。Scala更进一步对传递给方法的参数也进行某种程度的类型推断,特别是对(作为参数的)一等函数。如果已知一个方法接受一个函数参数,编译器能够推断出函数字面量(function literal)里面使用的类型。


47e3cb751db7515645499e1f7bbf8d2d187f1045

1.3.3 抛开语法
Scala语法采取了一种策略:如果一行代码的含义非常明确,就可以抛弃掉一些冗长的语法。这个特性可能会让刚学习Scala的用户感到困惑,但如果使用得当,这个特性是极其强大的。我们来看个重构代码的例子,从一个完全符合Scala标准语法的代码,简化为Scala社区的惯用写法。下面是Scala实现的Quicksort函数。


49e272d17292da27dfc536c0732fbcd4065aa878

这段代码接受一个T类型的列表,T可以被隐式转换为Ordered[T]类型(T <% Ordered[T])。我们会在第6章详细讨论类型参数及其约束,目前先不要关注于此。简单来说,我们需要一个列表,里面的元素是可以排序的,所以该元素应该有个判断是否小于的方法“<”。然后我们检查列表,如果是空列表或Nil,我们就返回个Nil列表。如果列表里有内容,我们取出列表的头(x)和尾(xs)。我们用头元素来把尾列表切分成两个列表。接着我们对这两个列表分别递归调用quicksort方法。在同一行上,我们把排序后的列表和头元素结合为一个完整的(排序后的)列表。
你可能会想,“哇哦,Scala代码看上去真丑”。就这个例子来说,你可能是对的。代码相当凌乱,难以阅读。有很多语法噪音掩盖了代码本身的含义。不仅如此,qsort后面还有很多吓人的类型信息。让我们拿出手术刀,割掉那些讨厌的玩意。首先,Scala可以自行推断分号。编译器会假定一行结束就是一个表达式的结束,除非你在行末留了什么未完结的语法片段,比如方法调用前的那个“.”(like the . before a method call)。
光删除分号显然没多大帮助。我们还需要应用“操作符”(operator notation)。操作符是Scala的一个能力,可以把方法当作操作符。无参数的方法可以用作后缀操作符(postfix operator),只有一个参数的方法可以当作中缀操作符(infix operator)。还有一些对特殊字符的专门规定,比如方法名的最后一个字符如果是“:”,则方法的调用方向反转。下面的代码演示了这些规则。


eb7438dcb561e86b270f7166db9282a10b0bf633

在定义匿名函数时(又称lambda),Scala提供了占位符语法。可以使用“_”关键字作为函数参数的占位符。如果使用多个占位符,每个相应位置的占位符对应于相应位置的参数。这种写法通常在定义很简单的函数时使用,比如我们的Quicksort例子里面比较元素大小的那个函数字面量。
我们可以结合使用占位符语法和操作符来改进我们的快速排序算法。


e0dfe7ed6f6eb8b0d4c1622e45dc50a0dd065ea1

Scala不仅为简单场景提供了语法糖,它还提供了隐式转换和隐式参数机制让我们可以扭曲(bend)类型系统。

1.3.4 隐式转换概念早已有之
Scala隐式转换是老概念的新用法。我是在C++的基础类型上第一次接触到隐式转换的概念。只要不损失精度,C++可以自动转换基础类型,比如我可以在声明long值的时候给它个int。对于编译器来说,实际的类型“double”,“float”,“int”和“long”型都是不同的,但在混用这些值时编译器尝试智能地去“做正确的事”(Do the Right Thing(TM))。Scala提供了相同的机制,但是是作为一个语言特性给大家使用(而不是只让编译器用)。
Scala会自动地加载scala.Predef对象,使它的成员方法对所有程序可用。这样可以很方便地给用户提供一些常用函数,比如可以直接写println而不是Console.println或System.out.println。Predef还提供了“基础类型拓宽”(primitive widenings)。也就是一些能够把低精度类型自动转换为高精度类型的隐式转换。我们来看一下为Byte类型定义的转换方法。


5bcbdf6a1ad1f68df46b6d3fcdb98e4a901e1e15

这些方法只是简单地调用运行时转换(runtime-conversion)方法。方法名前面的implicit关键字说明编译器会在需要对应的类型时尝试对Byte调用对应的方法。比如说,如果我们给一个需要Short类型的方法传了一个Byte,编译器会调用隐式转换方法byte2short。Scala还把这个机制更进一步:如果对一个类型调用一个它没有的方法,Scala也会通过隐式转换来查找这个方法。这比仅仅提供基础类型转换提供了更多的便利。
Scala也把隐式转换机制作为扩展Java基础类型(Integer、String、Double等)的一种手段。这允许Scala直接使用Java类以方便集成,同时提供更丰富的方法(method)以便利用Scala更为先进的特性。隐式转换是非常强大的特性,也因此引起一些人的疑虑,关键在于知道如何以及何时使用隐式转换。

1.3.5 使用Scala的implicit关键字
用好隐式转换是操纵Scala类型系统的关键。隐式转换的基础应用场景是按需自动地把一种类型转换为另一种,但它也可以用于有限形式的编译时元编程(limited forms of compiler time metaprogramming)。要使用隐式转换必须把它关联到某个作用域。可以通过伴生对象或明确的导入来做关联。
implicit关键字在Scala里有两种不同用法。第一种用法是给方法声明一种特殊参数,如果编译器在作用域里找到了合适的值就会自动传递给方法。这可以用来把某API的某些特性限定在某个作用域里。因为implict采用了继承线性化(inheritance linearizion)的查找策略,所以可以用来修改方法的返回值。这使用户可以写出非常高级的API以及玩一些类型系统的小把戏,在Scala collections API里就使用了这种技术。这些技术会在第7章详加解释。
implicit关键字的另一种用法是把一种类型转换为另一种。有两种场景会发生隐式转换,第一种场景是当你给一个函数传递参数的时候,如果Scala发现函数需要的参数类型(跟传给它的)不一样,Scala会首先检查类型继承关系,如果没找到,就会去查找有没有合适的隐式转换方法。隐式转换方法只是普通的方法,用implicit关键字做了标注,该方法接受一个参数,返回某些结果(译著:实际上是接受转换前的参数,返回转换后的结果,然后Scala用转换后的结果作为参数去调之前那个函数)。第二种场景是当调用某类型的某方法时,如果编译器发现该类型没有这个方
法,Scala会对该查找适用于该类型的隐式转换,直到找到一个转换后具有该方法的结果,或者找不到(编译出错)。这种做法在Scala的“pimp my library”模式中得以应用,这些内容也会在第7章详解。
这些特性的组合给Scala带来了非常有表达力的语法,同时保持其高级的类型系统。创造有表达力的库需要深入理解类型系统,也必须彻底理解隐式转换的知识。第6章会全面地覆盖类型系统的知识。Scala类型系统跟Java也能很好地交互,这是Scala的关键设计之一。

相关文章
|
6月前
|
IDE Java 编译器
scala的两种变量类型 var 和 val
scala的两种变量类型 var 和 val
143 2
scala的两种变量类型 var 和 val
|
安全 Java 大数据
大数据开发基础的编程语言的Scala的类型系统
Scala是一种强类型的编程语言,它具有一套完善的类型系统。本文将介绍Scala的类型系统,帮助开发者了解这门语言的类型安全性和灵活性。
93 0
|
大数据 Java 编译器
Scala 字符类型|学习笔记
快速学习 Scala 字符类型。
174 0
|
编译器 Scala 容器
Scala的Higher-Kinded类型
Scala的Higher-Kinded类型
|
Java Scala
Scala的存在类型
Scala的存在类型
|
编译器 Scala
Scala教程之:静态类型(三)
Scala教程之:静态类型(三)
|
存储 Java Scala
Scala教程之:静态类型(二)
Scala教程之:静态类型(二)
|
安全 Java Scala
Scala教程之:静态类型 (一)
Scala教程之:静态类型 (一)
|
Scala
scala简要:高级函数和高级类型
版权声明:本文为半吊子子全栈工匠(wireless_com,同公众号)原创文章,未经允许不得转载。
834 0