Swift 的编程范式
编程范式是程序语言背后的思想。代表了程序语言的设计者认为程序应该如何被构建和执行。常见的编程范式有:过程式、面向对象、函数式、泛型编程等。
一些编程语言是专门为某种特定范式设计的,例如,C 语言是过程式编程语言;Smalltalk 和 Java 是较纯粹的面向对象编程语言;Haskell、Scheme、Clojure 是函数式编程语言。
另外一些编程语言和编程范式的关系并不一一对应,如 Python、Scala、Groovy 同时支持面向对象和一定程度上的函数式编程。Swift 也是支持多种编程范式的编程语言。
由于代表了语言背后的思想,编程范式很大程度上决定了语言会呈现为何种面貌。用不着深入学习,仅仅浏览代码,就能发现 Scala 和 Swift 很类似,这是因为它们支持的编程范式是类似的;Scheme 和 Swift 看起来就相差很远,这是因为它们支持的编程范式很不一样。对于理解一门编程语言而言,相对于语言的语法和编写经验,理解语言的编程范式更重要。因为,就像看一本书,琢磨作者如何用词,如何构建章节是很重要,但更重要的是理解书所要表达的思想。
Swift 即支持面向对象编程范式,也支持函数式编程范式,同时还支持泛型编程。Swift 支持多种编程范式是由它的目标决定的。Swift 创造的初衷就是提供一门实用的工业语言。不同于 Haskell 这类出自大学和研究机构的具有学术性质的编程语言。苹果推出 Swift 时就带着着明确的商业目的:Mac OS 和 iOS 系统的主要编程语言 Objective-C 已显老态,Swift 将使得苹果系统的开发者拥有一门更现代的编程语言,从而促进苹果整个生态圈的良性发展。
Swift 的设计和开发无不体现着“实用的工业语言”这一目标。这决定了 Swift 无法做极端的语言实验,它需要在理智地面对现实的基础上,谨慎地寻求突破。这就决定了 Swift 需要继承历史遗产,在照顾现在大多数程序员的现实需求基础上,面向未来有所发展。
面向对象
面向对象编程的核心概念是继承,多态,和封装。以对象构建程序的基本单元的面向对象编程语言中,继承提供了一种复用代码的方法;多态提供了更高的抽象能力,使得我们可以设计出更通用的程序;封装提供一种使用代码更为便捷安全的机制。Swift 拥有以上所有的面向对象特性。所以,Swift 是一门完备的面向对象编程语言。
Swift 继承了 Objective-C 面向对象方面的主要特性,提供以类为主的封装和继承机制。但给予了结构体(Struct)和枚举(Enum)更丰富的面向对象特征,使它们可以用于封装更为复杂的对象。另外,相对于 Objective-C,Swift 是一门更为安全的语言。
单继承,多协议
在继承上,Swift 不同于 C++ 可以继承一个或者若干个类,而类似于 Objective-C 和 Java,只能单继承。但 Swift 可以实现多个协议(Java 中对应的是接口 Interface)。这在一定程度上弥补了没有多继承的局限,同时又避免了多继承难以控制的缺陷。
除了实现协议,Swift 还可以实现多个扩展(Extension)。扩展是一种向已有的类,枚举或者结构体添加新功能的方法。扩展和 Objective-C 中的分类(Category)类似,但与 Objective-C 中的分类不同的是,Swift 中的扩展没有名字。
更强大的结构体,枚举
C++ 和 Java 等大部分面向对象编程语言主要以类(Class)作为实现面向对象的基本结构。Swift 则赋予了结构体(Struct)和枚举(Enum)更多的面向对象特征,使结构体和枚举也能承担部分数据封装工作。在其他一些语言需要用类来解决的场景中,Swift 可以使用结构体和枚举类型,而且更为合适。例如,Swift 的 Array 和 Dictionary 是用结构体实现,而不是用类实现的,这不同于大多数编程语言。
Swift 的结构体和枚举可以像类一样,完成下列事情:
- 定义属性
- 定义方法
- 拥有构造器
- 可以被扩展(Extension)
- 可以遵守协议 (Protocol)
在封装这一点上,结构体和枚举几乎和类完全一致。不同的地方是,结构体和枚举是不能继承或者被继承的。所以,这两种数据类型也就没有多态性。
总结一下,Swift 中的类和其他面向对象编程语言的类一样是面向对象语言的核心概念,具有面向对象的基本特征。Swift 的结构体和枚举拥有比其他面向对象编程语言更多的面向对象特性,可以封装更复杂的对象。但不可继承,也就没有了多态性。
更多的值类型,而不是引用类型
结构体,枚举与类的另外一个区别是:结构体和枚举是值类型,而类是引用类型。
值类型在赋值和作为函数参数被传递时,实际上是在进行复制,操作的是对象的拷贝。Swift 中有大量值类型,包括 Number,String,Array,Dictionary,Tuple,Struct 和 Enum 等。
引用类型在赋值和作为函数参数被传递时,传递的是对象的引用,而并不是对象的拷贝。这些引用都指向同一个实例。对这些引用的操作,都将影响同一个实例。
在 Swift 中区分值类型和引用类型是为了将可变的对象和不可变的数据区分开来。可变的对象,使用引用类型;不可变的数据,使用值类型。值类型的数据,可以保证不会被意外修改。值类型的数据传递给函数,函数内部可以自由拷贝,改变值,而不用担心产生副作用。在多线程环境下,多个线程同时运行,可能会意外错误地修改数据,这常常会是一种难以调试的 bug。而使用值类型,你可以安全地在线程间传递数据,因为值类型传递是拷贝,所以无需在线程间同步数据变化。这就可以保证代码线程环境下的安全性。
结构体是值类型,暗示了结构体应该主要用于封装数据。例如,三维坐标系中的点 Point,代表几何形状的大小的 Size 等。而类是引用类型,意味着类应该用于封装具有状态的,可以继承的对象。例如,人,动物等。
Swift 中,Array、Dictionary、String 都是值类型,它们的行为就像 C 语言中的 Int 一样。你可以像使用 Int 一样简单安全地使用 Array,而不用考虑深度拷贝之类烦人问题。Swift 增强了对值类型的支持,鼓励我们使用值类型。因为值类型更安全。更多地使用值类型,将有助于我们写出行为更可预测,更安全的代码。
更安全的语言
类型安全语言
Swift 是强类型语言,这意味着 Swift 禁止错误类型的参数继续运算。例如,你不能让 String 和 Float 相加。这与 C#和 Java 一致;而与 C 和 Javascript 这类弱类型语言不一样。
Swift 是静态类型语言,这意味着 Swift 中变量是在编译期进行类型检查的。编译时,编译器会尽力找出包括类型错误在内的相关错误。例如,String 和 Int 相加这种类型运算错误,编译器在编译时就能告诉你,而不会在运行时才报错。这与 C#和 Java 一致;而与 Python 和 Ruby 这类动态类型语言不一样。
Swift 不允许不正确的类型运算或类型转换发生,所以 Swift 是类型安全的。
Swift 支持类型推导,并且有一个相当不错的类型推导器。大部分情况下,你都不用声明类型,编译器可以根据上下文为你推导出变量的类型。
安全的初始化过程
Swift 中类(包括结构体和枚举)的初始化过程类似于 Java 的设计。Swift 有一类特别的方法,被作为初始化方法,它们没有 func 前缀,而是以 init 为方法名。这不同于 Objective-C 中的初始化方法只是一个普通的方法。对于初始化方法的特殊处理可以在语言机制上保证初始化方法只被调用一次。这种机制在 Objective-C 中是不存在的,在 Objective-C 中,初始化方法就像其它的普通方法一样,可以被多次调用。
Swift 中初始化方法必须保证所有实例变量都被初始化。Swift 初始化方法要求特殊的初始化顺序。先保证当前类的实例变量被初始化,再调用父类的初始化方法完成父类实例变量的初始化。
Swift 保证了初始化方法只会被调用一次,同时所有的实例变量都会被初始化。这使得 Swift 初始化过程很安全。
安全的重写
Swift 提供了重写(Overriding)保护机制。如果要重写基类的方法,就必须在子类的重写方法前加上 overriding 关键字。这么做是向编译器声明你想提供一个重写版本。编译器会确认,基类里是否存在具有相同方法定义的方法。如果,基类中没有相同的方法定义,编译器就会报错。另一方面,如果没有加上 overriding 关键字的方法和基类的某个方法的定义相同,编译器也会报错,以防止意外的重写行为。这样就能从两方面保证重写行为的正确性。
Optionals
Swift 中的 Optionals 让我们能够更安全地应对有可能存在,也有可能不存在的值。在 Objective-C 里我们主要依靠文档来了解一个 API 是否会返回 nil。Optionals 则让我们将这份责任交给了类型系统。如果 API 的返回值声明为 Optional,就表示它可以是 nil。如果它不是 Optional,就表示它不可能是 nil。
在 Swift 中,类型后面加问号声明 Optional 类型,以及感叹号! 对 Optional 类型拆包都只是语法糖。Optionals 其实是由枚举实现的:
enum Optional<T> : Reflectable, NilLiteralConvertible { case None case Some(T) //... }
也就是说,Optional 其实是一种枚举类型。我们通过语言的类型系统来明确可能为 nil 的情况。这比 Objective-C 中使用文档来说明要安全得多。
面向对象编程总结
现在绝大部分程序员的工作语言仍然是面向对象编程语言。大部分流行的现代编程语言都会允许你创建对象。面向对象编程语言易于建模。因为,对象和类似乎很容易和现实世界中的事物和概念对应。但编程实践表明,任何东西都成为对象并不是一件好事情。举一个 Java 中的蹩脚例子:Java 中只有对象才能作为参数传入函数(当然还有原始类型 Primitive Type),所以为了将函数作为参数传递给另一个函数,需要将函数包裹在一个对象中,通常会使用一个匿名类(这也是 Java 中,监听器 Listener 通常的实现方法),而这个类不会有其他作用,只是为了满足 Java 一切皆为对象的设计,从而通过编译。
Java 拥有纯粹的面向对象概念。它从设计之初,就希望以一切皆为对象的纯对象模型来为世界建模。但发展到现在,Java 中加入了越来越多非对象的东西。引入了闭包,从而获得了函数式编程中的一级函数;引入泛型,从而获得了参数化的类型。这可能暗示了,这个世界是如此丰富多彩,使用单一模型为世界建模并不会成功。
Swift 在追求统一纯粹的编程范式这一点上并不固执。Swift 完整地支持面向对象编程,拥有完备的面向对象基础概念。这使得熟悉面向对象编程的程序员学习和使用 Swift 的成本降低了。Java 或者 Objective-C 程序员对 Swift 的很多概念会觉得很熟悉。对他们而言,学习 Swift 并不困难,很快就能将 Swift 投入到实际生产之中。
同时,Swift 还一定程度上支持函数式编程风格。在适合函数式编程的场景下,同时程序员又拥有函数式编程的思维和能力时,可以使用 Swift 以函数式的编程方法改善生产力。这将在下一章详细介绍。
函数式编程
函数式编程是一种以数学函数为程序语言建模的核心的编程范式。它将计算机运算视为数学函数计算,并且避免使用程序状态以及可变对象。函数式编程思想主要有两点:
- 以函数为程序语言建模的核心
- 避免状态和可变性
函数是函数式编程的基石。函数式编程语言的代码就是由一个个函数组合而成的。编写函数式语言的过程就是设计函数的过程。大规模程序由成千上万的函数组成,为了有效的组合这些函数。函数式编程语言,会尽量避免状态,避免可变对象。没有可变的状态,就使得函数式语言中的函数变为了纯函数。纯函数更容易模块化,更容易理解,对于复用是友好的。
函数
函数式编程的核心是函数,函数是“头等公民”。这就像面向对象语言的主要抽象方法是类,函数式编程语言中的主要抽象方法是函数。Swift 中的函数具有函数式语言中的函数的所有特点。你可以很容易地使用 Swift 写出函数式风格的代码。
高阶函数,一级函数
高阶函数,指可以将其他函数作为参数或者返回结果的函数。
一级函数,进一步扩展了函数的使用范围,使得函数成为语言中的“头等公民”。这意味函数可在任何其他语言构件(比如变量)出现的地方出现。可以说,一级函数是更严格的高阶函数。
Swift 中的函数都是一级函数,当然也都是高阶函数。
前文中举过 Java 中为了将函数作为参数传递给另外一个函数,需要将函数包裹在一个多余的匿名类中的蹩脚例子。Swift 函数都是一级函数,可以直接将函数作为参数传递给另外一个函数。这就避免了 Java 里出现的这种多余的匿名类。
闭包
闭包是一个会对它内部引用的所有变量进行隐式绑定的函数。也可以说,闭包是由函数和与其相关的引用环境组合而成的实体。函数实际上是一种特殊的闭包。
Objective-C 在后期加入了对闭包支持。闭包是一种一级函数。通过支持闭包,Objective-C 拓展其语言表达能力。但是如果与 Swift 的闭包语法相比,Objective-C 的闭包会显得有些繁重复杂。 以下示例显示了 Swift 闭包语言的简洁和优雅:
let r = 1...3 let t = r.map { (i: Int) -> Int in return i * 2 }
该例中,map 函数遍历了数组,用作为函数参数被传入的闭包处理了数组里的所有元素,并返回了一个处理过的新数组。例子中可以看到,Swift 中使用{}
来创建一个匿名闭包。使用in
来分割参数和返回类型。在很多情况下,由于存在类型推导,可以省略类型声明。
不变性
在介绍 Swift 的不变性之前,先讨论一下 Haskell 这门纯函数式语言。这将有助于我们对于不变性有更深刻的理解。
简单而言,Haskell 没有变量。这是因为,Haskell 追求更高级别的抽象,而变量其实是对一类低级计算机硬件:存储器空间(寄存器,内存)的抽象。变量存在的原因,可以视为计算机语言进化的遗迹,比如在初期直接操作硬件的汇编语言中,需要变量来操作存储过程。而在计算机出现之前,解决数学计算问题都是围绕构建数学函数。数学中,不存在计算机语言中这种需要重复赋值的变量。
Haskell 基于更抽象的数学模型。使用 Haskell 编程只需专注于设计数据之间的映射关系。而在数学上,表示两个数据之间映射关系的实体就是函数。这使得编写 Haskell 代码和设计数学函数的过程是一致的,Haskell 程序员的思路也更接近数学的本质。Haskell 摒弃了变量的同时,也抛弃了循环控制。这是因为没有变量,也就没有了控制循环位置的循环变量。这也很好理解。回忆一下我们在学习计算机之前的数学课程中,也无需使用到 for 这类概念。我们还是使用函数处理一个序列到另外一个序列的转换。
不变性导致另外一个结果,就是纯函数。没有可变的状态,没有可变对象,就使得函数式语言中的函数变为了纯函数。纯函数即没有副作用的函数,无论多少次执行,相同的输入就意味着相同的输出。一个纯函数的行为并不取决于全局变量、数据库的内容或者网络连接状态。纯代码天然就是模块化的:每个函数都是自包容的,并且都带有定义良好的接口。纯函数具有非常好的特性。它意味着理解起来更简单,更容易组合,测试起来更方便,线程安全性。
Swift 提供了一定程度的不变性支持。在 Swift 中,可以使用var
声明普通的变量,也可以使用let
快捷方便地声明不变量。
// 变量 var mutable // 不变量 let immutable = 1
Swift 区分var
和let
是为了使用编译器来强制这种区分。Swift 中声明了不变量,就必须在声明时同时初始化,或者在构造器中初始化。除这两个地方之外,都无法再改变不变量。Swift 中鼓励使用不变量。因为,使用不变量更容易写出容易理解,容易测试,松耦合的代码。
不变性有诸多好处。
- 更高层次的抽象。程序员可以以更接近数学的方式思考问题。
- 更容易理解的代码。由于不存在副作用,无论多少次执行,相同的输入就意味着相同的输出。纯函数比有可变状态的函数和对象理解起来要容易简单得多。你无需再担心对象的某个状态的改变,会对它的某个行为(函数)产生影响。
- 线程安全的代码。这意味着多线程环境下,运行代码没有同步问题。它们也不可能因为异常的发生而处于无法预测的状态中。
不像 Haskell 这种纯函数式编程语言只能申明不可变量,Swift 提供变量和不可变量两种申明方式。程序员可以自由选择:在使用面向对象编程范式时,可以使用变量。在需要的情况下,Swift 也提供不变性的支持。
惰性求值
惰性计算是函数式编程语言的一个特性。惰性计算的表达式不在它被绑定到变量之后就立即求值,而是在该值被取用的时候求值。惰性计算有如下优点。
- 首先,你可以用它们来创建无限序列这样一种数据类型。因为直到需要时才会计算值,这样就可以使用惰性集合模拟无限序列。
- 第二,减少了存储空间。因为在真正需要时才会发生计算。所以,节约了不必要的存储空间。
- 第三,减少计算量,产生更高效的代码。因为在真正需要时才会发生计算。所以,节约那部分没有使用到的值的计算时间。例如,寻找数组中第一个符合某个条件的值。找到了之后,数组里该值之后的值都可以不必计算了。
纯函数式编程语言,如 Haskell 中是默认进行惰性求值的。所以,Haskell 被称为惰性语言。而大多数编程语言如 Java、C++ 求值都是严格的,或者说是及早求值。Swift 默认是严格求值,也就是每一个表达式都需要求值,而不论这个表达式在实际中是否确实需要求值。但是,Swift 也提供了支持惰性求值的语法。在需要惰性时,需要显式声明。这为开发者在 Swift 中使用惰性提供了条件。
下面的例子展示了将默认是严格求值的数组变为惰性序列:
let r = 1...3 let seq = lazy(r).map { (i: Int) -> Int in println("mapping \(i)") return i * 2 } for i in seq { println(i) }
将获得如下结果:
mapping 1 2 mapping 2 4 mapping 3 6
结果显示 seq 是一个惰性序列。它的值只有在需要时才会真正发生计算。
函数式编程总结
函数式编程语言并不年轻,它的历史和面向对象编程一样悠久。1958 年被创造出来的 Lisp 是最古老的函数式编程语言。它比 C 语言年代更为久远。但直到最近,函数式编程思想才逐渐被重视。几乎所有新发明的编程语言都或多或少受到了函数式编程思想的影响。Python、Scala、Groovy、Swift 都有一级函数,闭包。使得你可以将函数直接传给另外一个函数,函数也能够以返回值形式被另一个函数返回。消除状态,提供不变性的好处越来越多被接受,Scala、Groovy、Swift 都提供了声明不可变对象的方法,以支持编写更趋近于函数式风格的代码。
函数编程语言有其优秀的地方,也许将来会成为一个重要的编程范式。但是,函数式编程的重要性可能更多会间接地体现在影响其他编程语言的发展上。未来,可能很难出现一门主要以函数式编程范式设计的主流编程语言。如同 Java 这样的以单一编程范式(面向对象)构建,而成为主流的编程语言的机会应该不会太多了。如同 Haskell 这样追求纯粹的函数式编程语言,更多的可能只是一个偏学术的语言实验。
容我再重复一次上一节提到的理由:这个世界是如此丰富多彩,使用单一模式为世界建模可能并不会成功。当然,这类预测常常会被打破。如果,将来计算机领域出现了能解决所有问题的统一范式,我将很乐意再次学习和讨论它。但如果仅仅讨论现状的话,我们仍然不得不面对一个分裂和折衷的世界。
Swift 并不是一门主要以函数式编程范式构建的语言,它更多的是借鉴融合了函数式编程一些优秀思想(更灵活强大的函数,不变性的优点)。Swift 在大多数的场景下,仍然主要会以面向对象编程语言的面目出现。因为,作为另一门面向对象编程语言 Objective-C 的继任者,Swift 需要继承 Objective-C 的遗产:Cocoa。我们现在写 Swift 代码,大部分时候还是在 Cocoa 框架之上,可以说 Cocoa 就是 Swift 的标准库。在一个主要以面向对象语言编写的框架中写代码,最合适的思维方式仍然会是面向对象的。Cocoa 可以说是 Swift 得以在高起点出发的基础,也可以说其发生胎换骨变化的阻碍。
Swift 对函数式编程的支持,使得程序员多了一种选择。Swift 并不强迫程序员一定要以面向对象的方法思维。在场景合适的情况下,程序员可以选择使用函数式风格编写代码。如果确实是合适的场景,就能够改善生产力。
面向对象与函数式编程
如果,我们按语言范式给现在流行的语言分类,支持面向对象的编程语言应该会是最长的队伍。现在大部分流行的现代编程语言都是面向对象的,它们都会允许你创建对象。但同时,你会发现比较流行的几个编程语言,Python、Scala 甚至 Java 都或多或少都受到了函数式编程语言的影响。它们都引入一些函数式编程的概念,可以在一定程度上编写出具有函数式风格的代码。
在熟悉了类面向对象编程语言之后,再接触函数式编程语言,常常会觉得耳目一新,甚至隐约觉得函数式语言会是救世良方。那我们是否应该就此彻底转向函数式编程语言呢?使用 Haskell 来拯救世界?
面向对象编程语言在大规模实践之后,我们确实更深刻地了解了它们的缺点(例如,难以编写多线程环境下的软件应用;继承并不是代码复用的好方法)。函数式语言也确实有不少优点,有些优点恰恰就能解决面向对象语言的问题(纯函数十分适应多线程环境,纯函数天生就是模块化的,对于代码复用十分友好)。但是,函数式编程也许也存在某些问题。而这些问题,可能要在更大规模的业界实践之后才会暴露出来。现在我们已经认识到,单纯以对象为世界建模是有困难的。那么以数学模型来为世界建模可能也并不会好到哪里去。而可以确信的是,它们都有自己各自擅长的领域和环境。我们仍然还无法使用某种单一的编程范式来解决所有问题。
更大的现实是无数企业已经在面向对象编程语言上做了巨大的投资,即使现在面向对象编程已经暴露出一些问题,而函数式编程又呈现出不少能解决这些问题的优点,任何一个谨慎的人都不会,也不可能马上抛弃面向对象编程,彻底全面地转向函数式编程语言。
现实的选择是支持面向对象编程的同时,提供函数式的支持。这样,在大部分面向对象游刃有余的地方,仍然可以使用面向对象的方法。而在适合函数式编程的地方,而你又拥有函数式编程的思维和能力时,还可以采用函数式的编程方法改善生产力。
Swift 就是这样一个现实的选择。完善的面向对象支持,使 Swift 继承了 Objective-C 遗留下来的丰厚遗产。在 Swift 中使用 Objective-C 对象并不复杂。如果,你遇到一个对多线程安全性有要求的场景,需要使用函数式风格编写这部分代码,这在 Swift 中也是很轻松的。
泛型编程
泛型编程是另外一个有趣的话题。泛型为程语言提供了更高层级的抽象,即参数化类型。换句话说,就是把一个原本特定于某个类型的算法或类当中的类型信息抽象出来。这个抽象出来的概念在 C++ 的 STL(Standard Template Library)中就是模版(Template)。STL 展示了泛型编程的强大之处,一出现就成为了 C++ 的强大武器。除 C++ 之外,C#、Java、Haskell 等编程语言也都引入了泛型概念。
泛型编程是一个稍微局部一些的概念,它仅仅涉及如何更抽象地处理类型。这并不足以支撑起一门语言的核心概念。我们不会听到一个编程语言是纯泛型编程的,而没有其他编程范式。但正因为泛型并不会改变程序语言的核心,所以在大多数时候,它可以很好地融入到其他编程范式中。C++、Scala、Haskell 这些风格迥异的编程语言都支持泛型。泛型编程提供了更高的抽象层次,这意味着更强的表达能力。这对大部分编程语言来说都是一道美味佐餐美酒。
在 Swift 中,泛型得到广泛使用,许多 Swift 标准库是通过泛型代码构建出来的。例如 Swift 的数组和字典类型都是泛型集合。这样的例子在 Swift 中随处可见。
泛型函数
Swift 函数支持泛型。泛型函数通过将函数参数和返回值定义为泛型类型,使得函数可以作用于任何适合的类型。下面展示了一个简单的泛型函数:
func swapTwoValues<T>(inout a: T, inout b: T) { let temporaryA = a a = b b = temporaryA }
泛型类型
除了泛型函数之外,Swift 还可以自定义泛型类,泛型结构体和泛型枚举。这样的泛型类型可以作用于任何类型,其用法和 Swift 提供的 Array 和 Dictionary 相同。
用一个栈(Stack)的例子展示泛型结构体的定义和使用。泛型枚举和泛型类的定义和使用方法是相同的。
// 定义一个泛型结构体 struct Stack<T> { var items = [T]() mutating func push(item: T) { items.append(item) } mutating func pop() -> T { return items.removeLast() } } // 使用一个泛型结构体 var stackOfStrings = Stack<String>() stackOfStrings.push("uno")
泛型类型参数 T 被用在了三个地方:
- 创建数组 items 时,指定了 items 中可以存储的数据类型;
- 指定了函数 push 的参数类型;
- 指定了函数 pop 的返回值类型。
泛型协议
而对于协议,Swift 中没有提供类似结构体或类那样的方法来定义泛型协议。但我们可以使用 typealias 关键字定义该协议的关联类型,这样一定程度上可以模拟泛型协议的效果,例子如下:
protocol GeneratorType { typealias Element mutating func next() -> Element? }
实现该协议的类必须定义一个别名为 Element 的关联类型。这和泛型的概念异曲同工,一定程度上实现了泛型协议。
泛型约束
在泛型的编程实践中,我们会遇到一些需要对泛型类型做进一步约束的场景。类型约束为泛型参数指定了一个类型,或者要求其实现某个特定的协议。比如,`意味着泛型参数指代的对象需要遵守 Equatable 协议。
类型约束对泛型参数的类型做了一定约束,可以强制要求泛型参数代表的类型遵守某个协议。而 where 语句可以更进一步对类型约束中声明的泛型参数所需要遵守的协议作出更详细的要求。where 语句也可以对协议的关联类型作进一步约束。比如,你可以要求两个泛型参数所遵守的协议的关联类型是相同的。
泛型编程总结
总体而言,Swift 提供了全面的泛型编程语法,让程序员可以写出抽象层次更高,更为灵活的代码,在避免了重复代码的同时,又能拥有良好的类型安全性。
总结
最后总结一下,Swift 是一门典型的多范式编程语言,支持面向对象是为了继承面向对象编程丰厚的成果;支持函数式编程,是为了探索新的可能;支持泛型编程,则是一道美味的佐餐美酒。
Swift 允许程序员在大部分使用面向对象就游刃有余的时候,轻松地继续使用面向对象编程;而在适合函数式编程的场景下,同时程序员又拥有函数式编程的思维和能力时,还可以使用 Swift 以函数式的编程方法改善生产力;以及任何时候程序员都可以在 Swift 中使用泛型,提高抽象层次。
参考文档
- Blog: airspeedvelocity
- Apple’s Swift blog
- Blog: objc.io
- Apple’s Document “Swift Programming Language”
感谢丁晓昀对本文的审校。
给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ , @丁晓昀),微信(微信号: InfoQChina )关注我们,并与我们的编辑和其他读者朋友交流(欢迎加入 InfoQ 读者交流群)。
评论