深入理解Scala - 复合语言

本文内容主要来自《Scala in Depth》的第一章。主要介绍Scala是一门集面向对象编程和函数编程的复合语言。

多范型语言

Scala除了是面向对象和函数式的语言外,它还是:

  • 泛型语言

  • 脚本语言

  • 命令式语言

  • 元语言

作为一门现代的编程语言,不可避免的要接受各种思考方式的影响。跟C++一样,语言本身提供的泛型支持很多,不一定适合各自的项目,需要依据自身情况选择合适的泛型。并且需要将这种选择规范化。并且Scala的书写方法更加灵活,也可以说是更加难以理解,对Scala的代码规范会更加重要。

Scala起源的主要理论支撑室函数式编程和Petri网络[注1]。

Scala的核心概念

由于面向对象和函数本身是不一样的分析问题的方法。对于一门糅合了这两种方法的语言,我们需要去尝试理解如何将它们有机地结合起来。对于Scala复合语言,要理解语言整合三个重要方面:

  • 面向对象和函数式的融合。面向对象的工作方式是定义和组合对象;函数式的工作方式是定义和组合函数。在Scala里面,函数也是对象。用Scala写程序,需要的不仅仅是定义和组合对象,还需要定义和组合函数。

  • 富有表现力的语言和静态类型。主流的静态类型语言,比如Java、C++要求明确的类型。这造成代码中夹杂了太多了类型繁复信息。一个最佳实例就是老版本C++中STL的一些代码。Scala的代码可以像动态类型语言,比如Ruby,非常简洁,但同时还是类型安全的。

  • 高级语言特性和深度的Java整合。Scala自身有很多高级的语法特性,比如柯里化、延迟求值等。但另一方面又要求整合Java,使Scala和Java能够互操作。

上面的3个方面既是我们想要的,但它们自身又是是矛盾的。Scala找到了它自己的整合方式。

动词VS名词

面对对象的核心是实体,是找到活动中的名词成分,然后定义它们的交互。函数式的核心是找到活动中的动词成分,然后将动作组合成一个连续的动作组合。我们举个例子:

猫抓住并吃掉了鸟。

一个面向对象的程序员看到这句话,就会知道两个实体:猫和鸟,还有两个动作:抓和吃。写出来的程序:

class Bird
class Cat {
  def catchBird(b: Bird): Unit =
    Console.println("Catch Bird")
  def eat(): Unit =
    Console.println("Eat Bird")
}
object CatBird extends scala.App {
  val cat = new Cat
  val bird = new Bird
  cat.catchBird(bird)
  cat.eat()
}

一个喜欢函数式的程序员看到这句话,想到的就不一样。他首先看到两个动作:抓和吃。对于抓这个动作,需要一个掠食者一个牺牲者,返回这个掠食者;对于吃这个动作,需要一个掠食者,吃了就该不饿了,饱了。因此他写出来的程序可能像:

trait Cat
trait Bird
trait Catch
trait FullTummy
class BlackCat extends Cat with Catch with FullTummy
class WhithBird extends Bird
object CatBird extends scala.App {
  def catchBird(hunter: BlackCat, prey: WhithBird): BlackCat =
  {
    Console.println("Catch Bird")
    hunter
  }
  def eat(consumer: BlackCat): BlackCat =
  {
    Console.println("Eat Bird")
    consumer
  }
  def story(hunter: BlackCat, prey: WhithBird) : Unit =
  {
    eat(catchBird(hunter, prey))
  }
  story(new BlackCat, new WhithBird)
}

由于使用Scala描述,上面的代码还交杂着面向对象的东西。由于函数还是需要有输入,输入就是我们的实体。因此函数式编程比面向对象,有更强的类型依赖。

我们来总结一下面向对象和函数式的软件开发方式:面向对象是一种自顶向下的代码设计方法。它先把软件分解为不同的名词或者对象,然后每个对象有ID(self/this)、方法,还有状态。在识别出所有的名词和它们的行为之后,名词之间的交互也就定义完成了。函数式将软件分解为不同的行为、动作,是一种自底向上的设计方法。函数式夹杂有很强的数学思维,行为和动作只是作用于输入,没有副作用。所有的变量都视为是不可变的。

Object-oriented programming Functional programming
Composition of objects (nouns)  Composition of functions (verbs)
Encapsulated stateful interaction  Deferred side effects
Iterative algorithms  Recursive algorithms and continuations
Imperative flow  Lazy evaluation
N/A  Pattern matching

Scala之外的函数式思维

由于函数式编程的很多好处。在Scala之外,我们常用的Java中都可以看到函数式思维的成功运用。举个例子,EJB。EJB中有Session Bean,它用来包含行为。Entity Bean,用来对系统中的实体进行建模。Stateless Session Bean有点类似于函数式中的集合。

最近的Java 8也新增加有函数接口。

Function和Predicate接口。

Scala为语言的表达能力所做的设计决策

主要的设计决策包括:

  • 类型在右边,修饰符在左边。

  • 类型推断

  • Scalable syntax

  • 隐式转化

对于类型放在左边还是右边,估计有不少争议。主要还是来自于继承古老C,而养成的习惯问题。我们做个比较,不下结论。

Variable type  C++  Java  Scala
Mutable integer variable int x int x  var x: Int
Immutable integer value  const int x final int x  val x: Int
Constant pointer to a volatile integer volatile int * const x  N/A  N/A
Lazily evaluated integer value N/A  N/A  lazy val x: Int

类型推断是Scala能够保持简洁,又是类型安全语言的根本原因。从本质上讲,静态类型并不需要显式地为编译器指定所有标示符的类型,原因在于编译器自身的技术原因。而现在,编译器能够做类型推断了。最新的C++也支持auto,类型推断肯定是主流的语言发展方向。

第三项,可变语法对软件工程来讲可能不是好消息。因为它增加了理解、交流的难度。比如:

  def qsort1[T <% Ordered[T]](list: List[T]): List[T] = {
    list match {
      case Nil => Nil;
      case x :: xs =>
        val (before, after) = xs.partition({
          i => i.<(x)
        })
        qsort1(before).++(qsort1(after).::(x))
    }
  }
  def qsort2[T <% Ordered[T]](list: List[T]): List[T] = list match {
    case Nil => Nil
    case x :: xs =>
      val (before, after) = xs partition (_ < x)
      qsort2(before) ++ (x :: qsort2(after))
  }

哪个看着好点呢?

隐式类型转换,如果使用过C++就该很熟悉了。原生类型之间只要不溢出可以相互转换,并且不需要显式指明。Scala在两种情况下会尝试隐式转换:一是,类型不匹配的时候;二是,改类型不存在某个方法的时候。

Scala和Java的互操作性

互操作涉及两个方面,一是Scala访问Java;二是Java访问Scala。我们只是看前者,后者使用的比较少,支持也不完整,也没有多少意义。

Scala的类继承结构图:

深入理解Scala - 复合语言_第1张图片

Scala的AnyRef等价于Java的Object。

需要注意的是Nothing和Null,它们是类型的下限。这在其他面向对象的语言里面是没有的。

在Scala里面使用Java是非常顺畅的。因为存在Scala到Java的直接映射。Java的class在Scala里面还是class。Java的interface在Scala里面是trait。Java的静态成员在Scala里面是object。Scala的包包含的使用方法,不管包含Scala包还是Java包,都可以像Java那样使用。

注1:Petri网是对离散并行系统的数学表示。Petri网是1960年代由卡尔&middot;A&middot;佩特里发明的,适合于描述异步的、并发的计算机系统模型。它的提出主要是解决形式既有语言缺少并行支持的问题。

你可能感兴趣的:(深入理解Scala - 复合语言)