以下内容基于 Tour of Scala

在快速浏览了一一遍 tour 中的内容后,我把 Scala 与其他常见的编程语言如 Java、Python、JavaScript 等在设计、结构、推荐写法等方面,做一个横向的对比。由于这份文档是基于 Scala2 的,而我本地调试用的是 Scala3,所以还会提及一些 2 和 3 的区别。

introduction

Basics

这个设计起初我不太理解,因为完全可以把后面的的参数变成默认唯一一组参数中的某个值,后面在讲到柯里化的一系列应用以及帮助类型推断方面有很大的用处。

  1. 副作用:影响 type inferred,影响性能
  2. 不符合 fp 的设定:尽量用表达式,而非语句

Unified Types

Class

Default Parameter Values

除了转成 java 调用时,会遇到问题,注意重新抽象或者必传所有参数。其他的与 Python 的使用没有差别。

Named Arguments

命名参数可以乱序,未命名的一定要在命名的之前,从 java 中调用还是有问题。

Traits

无法实例化,没有参数,非常适合用于泛型和抽象方法。
继承类中,需要实现 trait 的方法,包括遵循子类型。

Tuple

和 Python 的 tuple 基本一样,通过下标访问的方式是 _1
Pattern matching 这个部分,可以用于拆解赋值,也可以用于 for 循环中的匹配输出,还可以用于列表推导,还是很灵活的。

val numPairs = List((2, 5), (3, -7), (20, 56))
for ((a, b) <- numPairs) {
println(a * b)
}

这里 for 循环的倒箭头惊到我了hhh。
关于 tuple 和 case classes 的选择问题,case classes 的可读性会更好一些。

Class Composition with Mixins

class D extends B with C

这里 D 的父类是 B,mixin 就是 C。父类只有一个,mixin 可以有多个。
Trait 间的继承不需要实现父 trait 的方法。

trait RichIterator extends AbsIterator {
def foreach(f: T => Unit): Unit = while (hasNext) f(next())
}

这个写法就很 cool 了。

Higher-order Functions

参数是 function 或者返回值是 function 。这和 Python 的函数作为 first-class 是一个意思。
一个 map(function) 这样的例子。
另外一种方式是类似工厂模式,通过实现不同函数的细化某一类事情。
返回值是一个匿名函数。仔细想了一下,创建函数的必要性,有点像 Python 中的偏函数,当然也可以用其他方式去实现,只是这样会更直观,我返回的就是一个函数,而不需要像在 java 中做一些重载或者反复定义的事情了。

nested methods

函数的嵌套,这很函数式编程,很早的时候我看 SICP 和 c311 的时候用到 Scheme 这门 Lisp 方言,经常写一些回括号很多的套了好多表达式的代码。
对 scala 的 function 和 method 的定义区分又模糊了,在原有的 Python 或者 Node 中,我会认为 method 是 class 中的方法,function 是外面单独定义的命名或者匿名的,现在没有上下文的情况下,文档把这种东西叫 method,有点迷惑。

Multiple Parameter Lists (Currying)

Currying 这种东西在 JavaScript,尤其是一些前端的 code 中经常能看到,就是函数后接着好多组参数,每一个前面的结果是另一个函数,继续接受下一个参数,一直到末尾。

我对这种东西持有中立态度。理论上所有高阶函数都可以写成更易读的普通函数,这里还要看文档和逻辑支撑情况。
这里举了一个很好的例子,就是类型推断,如果用 flatten parameters 的话,匿名函数的参数需要显式指明,否则会编译失败。

def foldLeft1[A, B](as: List[A], b0: B, op: (B, A) => B) = ???
def notPossible = foldLeft1(numbers, 0, _ + _)

此时编译器仍然在推断 A 和 B。而如果改成多参数的话:

def foldLeft2[A, B](as: List[A], b0: B)(op: (B, A) => B) = ???
def possible = foldLeft2(numbers, 0)(_ + _)

编译器就能自己 infer 出来了。

偏函数也提了一下,在 3 中,柯里化已不再需要占位符。

其实这一节的重点是 implicit,虽然之前没有接触过,但是看了一些别人写的东西,感觉还是大有可为的。另外 scala 没有静态成员,可以把 implicit function 写在半生对象里。隐式转换只会触发一次,另外是在编译器查找方法失败的情况下才会触发,这里要特别注意一下。

Case Classes

一种主打 immutable 的类。实例化的时候不用 new,case class 能自己 apply ,参数尽量不用 var。比较时比较的是值,而不是引用,复制的时候,也是浅拷贝,这两个挺好的,用起来会比较顺手,不会出现某些语言大量地址引用,复制修改时要格外小心。

Pattern Matching

import scala.util.Random

val x: Int = Random.nextInt(10)

x match {
case 0 => "zero"
case 1 => "one"
case 2 => "two"
case _ => "other"
}

比 switch 写起方便一点;另一个例子中参数定义的是父类,但是传入 instance match 到具体子类(case class),比 java 的不停 cast 强了很多。另外就是可以通过 boolean expression 的方式去 match 某个参数值

case Email(sender, _, _) if importantPeopleInfo.contains(sender) =>
"You got an email from special someone!"

还可以只 match 类型

def goIdle(device: Device) = device match {
case p: Phone => p.screenOff
case c: Computer => c.screenSaverOn
}

sealed 密封这个概念也很有趣,让所有的子类型都在同一个文件里,不过这样确实保证了无 catch other 的状况。

Singleton Objects

全局的单例对象,非常适合用作工具方法。另外就是伴生对象和伴生类。这里提到了一个Option类别,类型如其名,当函数有可能不返回值的时候使用,Option有两个子类 SomeNone ,在这里 Understanding Some and Option in Scala - Stack Overflow 提到,用 Option 的好处是调用者都必须要检查值是否存在,而且不仅仅用在 match 这里,在任何 map ,flatMapgetOrElse中都可以使用。

Regular Expression Patterns

.r结尾是没想到的。虽然后面两节才介绍 for 循环,但是每次看到反箭头还是会别扭一下。

Extractor Objects

开始的时候会有点误会 applyunapply 是在做 serialization deserialization,实际上是在做 construction 和 deconstruction,解构这个概念挺好的,

For Comprehensions

for (enumerators) yield e 也可以不 yield 这样 for 语句就返回了 Unit。注意返回值的类型是由第一个 generator 决定的,如 xx <- xxList 那这返回的就是一个 List,然后 yield 的值的类型决定了 List 中 item 的类型。

Generic Classes

泛型类, calass Stack[A] 虽然也是用什么都行,之前看到的可能更多是在用 T。 elements = x :: elements 这里是个 prepending 如果是要 appending,则是 elements = elements :+ xNil表示的是空数组,如果我们创建一个空数组 var a = List.empty[String]Nil做一下比较会发现二者是一样的。子类型也可以传入泛型类。

Variances

这一部分讲的是协变和逆变。和 C# 一样,是在抽象类定义时就约定好的,而 Java 是在使用时通过 <? Extends T><? Super T> 实现的。在相当长的一段时间里,我并不懂为什么需要有逆变这种东西,这里关于 Java 的逆变的作用,Kotlin 的文档讲得挺好的,

Upper Type Bounds

class PetContainer[P <: Pet](p: P) {
def pet: P = p
}

T <: A 表示当T 为 A 的子类型时,构建这个 container。