Swift 与函数式编程

963 阅读24分钟

本文是一篇学习函数式编程的笔记,并非完全原创,很大一部分的内容来自于文末提及的博客和书籍。

2020 年的今天,函数式编程已经不是一个陌生的名词了,尽管如此,头条平时开发当中所写的代码,95%(可能更高)比例的代码,用的还是另外一种编程范式(paradigms)----命令式编程所写出来的。 由于篇幅和时间有限,本文不会讲一些复杂的概念如 functor, monad 等等,而是通过一些例子,并结合 Swift 语言,让你对函数式编程有更进一步的了解,以及,为什么要用函数式编程的方式来思考。

编程范式(Programming paradigm)

引用维基百科上的解释:

编程范式是一类典型的编程风格,是指从事软件工程的一类典型的风格。如:函数式编程、过程式编程、面向对象编程、指令式编程等等为不同的编程范式。

命令式编程(Imperative Programming)

一个 冯诺伊曼结构(存储程序型电脑)被抽象成5大组成部分: 控制器、运算器、存储器、输入设备、输出设备:

70多年过去了(注1),冯诺依曼结构依然统治者计算机行业,可见其伟大。

现代计算机设备是基于冯诺依曼结构的产物,而命令式编程则是面向计算机硬件的抽象,有变量(对应存储器),赋值语句(获取,存储指令),表达式(内存引用和算术运算)和控制语句(跳转指令),所谓的“高级语言”, 背后依然是冯诺依曼机器的影子:

而命令式编程,实际上就是冯诺依曼结构的指令序列,程序员需要通过指令,精确的告诉计算机干什么事情。因此,在使用命令式编程范式写代码时,程序员做的事情就是把人类语言翻译成精确的计算机语言指令。我们熟悉的面向对象编程就是命令式编程的一种。

早期的织布机,其实也是命令式编程的一种体现。

声明式编程(Declarative Programming)

声明式编程是一种编程范式,与命令式编程相对立。它描述目标的性质,让电脑明白目标,而非流程。声明式编程不用告诉电脑问题领域,从而避免随之而来的副作用。而命令式编程则需要用算法来明确的指出每一步该怎么做。

上面这句话也是摘自维基百科,给你 30 秒理解这句话。

好的时间到了,现在可以忘掉这句话了,因为对于不了解声明式编程的人来说,这句话基本毫无意义。

我还是举个例子吧:

一天我和我老婆准备去蓝色港湾附近一家很不错的日料吃晚饭,晚上 7 点,我俩到了蓝色港湾,走进饭店来到前台和服务员说:

  • 命令式编程(How):靠窗那边有一张桌子是空的,我和我老婆要走过去然后坐那。
  • 声明式编程(What): 请帮我安排一张两人桌谢谢。

是不是很简单?

我们常用的 SQL, 正则表达式都是声明式编程

命令式编程 vs. 声明式编程

  • 命令式编程(Imperative):详细的命令机器怎么(How)去处理一件事情以达到你想要的结果(What);
  • 声明式编程( Declarative):只告诉你想要的结果(What),机器自己摸索过程(How)

问题:
朋友第一次来你家聚餐,打电话和你说他在地铁站出口,问你怎么走,分别用命令式和声明式的方式回答。

需要注意的是,很多声明式的方法其实是通过封装了命令式的动作来实现的:

  • 日料店的服务员安排我们坐到座位上,是因为他知道把我们领到餐桌旁的所有步骤;
  • 而我只告诉朋友我家的地址,是建立在朋友手机上装了导航软件。

函数式编程(Functional Programming)

函数式编程中的函数这个词不是指计算机中的函数(实际上是 Subroutine, 中文翻译是子程序),而是指数学中的函数,即自变量的映射。也就是说一个函数的值仅决定于函数参数的值,不依赖其他状态。比如 sqrt(x) 函数计算 x 的平方根,只要 x 不变,不论什么时候调用,调用几次,值都是不变的。

从理论上说,函数式语言也不是在冯诺伊曼结构的机器上运行的,而是通过λ演算来运行的。λ演算是图灵完备(Turing completeness)的,但是大多数情况,函数式程序还是被编译成冯诺依曼机的机器语言的指令执行。

函数式编程有这些特性:

函数是一等公民(First Class)

一等公民指的是函数与其他数据类型一样可以赋值给其他变量,也可以作为其他函数的参数或者返回值。

let addOne =  { (a:Int) -> Int in
    return a + 1
}
let computeThenDouble = { (fn :@escaping (Int) -> Int, a:Int) -> Int in
    return fn(a) * 2
}
let ret = computeThenDouble(addOne, 1) // ret = 4

惰性求值(Lazy Evaluation)

在使用惰性计算时,表达式不在它被绑定到变量之后就立即求值,而是在该值被取用的时候求值。惰性计算有如下优点:

  • 首先,你可以用它们来创建无限序列这样一种数据类型。因为直到需要时才会计算值,这样就可以使用惰性集合模拟无限序列。

Haskell 的无限列表, [1..] 这是一个无限长的列表. 我们是无法在内存上直接放这么大的数组的, 但是通过惰性求值就能使用它. 从第一个元素开始获取, 用到几个计算几个, 这个就是惰性求值的例子.

  • 第二,减少了存储空间。因为在真正需要时才会发生计算。所以,节约了不必要的存储空间。
  • 第三,减少计算量,产生更高效的代码。因为在真正需要时才会发生计算。例如,寻找数组中第一个符合某个条件的值。

不可变数据(immutable data)

函数式编程语言中的变量也不是命令式编程语言中的变量,即存储状态的单元,而是代数中的变量,即一个值的名称。变量的值是不可变的 (immutable),也就是说不允许像命令式编程语言中那样多次给一个变量赋值。比如说在命令式编程语言我们写x = x + 1,这依赖可变状态的事实,拿给程序员看说是对的,但拿给数学家看,却被认为这个等式为假。

Edsger Dijkstra (注3)在他一篇著名的论文《GOTO 语句之害》(Go To Statement Considered Harmful) 中写道:

My second remark is that our intellectual powers are rather geared to master static relations and that our powers to visualize processes evolving in time are relatively poorly developed.

我的...观点是,以我们的智力,更适合掌控静态关系,而把随时间不断发展的过程形象化的能力相对不那么发达。

在其他类型的语言中,变量往往用来保存"状态"(state)。不修改变量,意味着状态不能保存在变量中。函数式编程使用参数保存状态,最好的例子就是递归。下面的代码是一个将字符串逆序排列的函数,它演示了不同的参数如何决定了运算所处的"状态"。

function reverse(string) {
    if(string.length == 0) {
        return string;
    } else {
        return reverse(string.substring(1, string.length)) + string.substring(0, 1);
    }
} 

由于使用了递归,函数式语言的运行速度比较慢,这可能也是长期不能在业界推广的主要原因。

没有副作用(No Side Effect)

所谓"副作用",指的是函数内部与外部互动(最典型的情况,就是修改全局变量的值),产生运算以外的其他结果。 函数式编程强调没有"副作用",意味着函数要保持独立,所有功能就是返回一个新的值,没有其他行为,尤其是不得修改外部变量的值。

副作用包括但不限于以下行为:

  • 改变一个类的一个成员属性的值
  • 将一些数据写入磁盘;
  • 改变某个按钮的颜色/文字/交互状态。
  • 往数据库插入记录
  • 发送一个 http 请求
  • 打印/log
  • 获取用户输入
  • 访问系统状态

引用透明(Referential transparency)

引用透明指的是函数的运行不依赖于外部变量或状态,只依赖于输入的参数,任何时候只要参数相同,引用函数所得到的返回值总是相同的。

基于前面的两点,即可得出这一点的结论。其他类型的语言,函数的返回值往往与系统状态有关,不同的状态之下,返回值是不一样的。这就叫"引用不透明",很不利于观察和理解程序的行为。

纯函数

具有引用透明特性的函数,称之为纯函数(Pure Function).

var xs = [1,2,3,4,5];

// 纯的
xs.slice(0,3);
//=> [1,2,3]

xs.slice(0,3);
//=> [1,2,3]

xs.slice(0,3);
//=> [1,2,3]


// 不纯的
xs.splice(0,3);
//=> [1,2,3]

xs.splice(0,3);
//=> [4,5]

xs.splice(0,3);
//=> []

使用纯函数的优势

可缓存性(Cacheable)

由于纯函数在输入相同的情况下总是能返回相同的输出,因此我们可以用一个 memoize 函数做缓存,以输入为 key, 返回值为 value 的字典来记录这个缓存:

func memoize(fn :@escaping (Int) -> Int) -> (Int) -> Int {
    var cache = [Int:Int]()
    return { (val : Int) -> Int in
        let value = cache[val]
        if value != nil {
            return value!
        }
        else {
            let newValue = fn(val)
            cache[val] = newValue
            return newValue
        }
    }
}
func add1(a: Int) -> Int {
    let sum = a + 1
    print(a, " + 1")
    return sum
}

let add1UseCache = memoize(fn: add1)
add1UseCache(1) // return 2, 打印出 1 + 1
add1UseCache(1) // return 2, 什么都不打印

可移植性/自文档化(Portable / Self-Documenting)

Erlang 语言的作者 Joe Armstrong 说过这么一句话:“面向对象语言的问题是,它们永远都要随身携带那些隐式的环境。你只需要一个香蕉,但却得到一个拿着香蕉的大猩猩...以及整个丛林”。

  • 由于固定的输入一定是固定的输出,并且函数不依赖外部变量,因此每个纯函数是高度内聚的,高度内聚的代码就意味着可移植性好;
  • 可移植性甚至可以把函数序列化(serializing)并通过 socket 发送,想象一下通过一个网络请求获取一个函数的场景?
  • 因为不依赖外部变量,所以任何影响输出的值都会被显式地传入,查看代码是也不必到处查看某个数据的来源,或者什么会其他的外部结果,因此纯函数的代码基本可以不用写注释和文档。

可测试性(Testable)

因为固定的输入一定是固定的输出,测试的结果也很容易得到验证。

QuickCheck 一个为函数式环境量身定制的测试工具,最早是一个用于随机测试的 Haskell 库。相较于独立的单元测试中每个部分都依赖特定输入来测试函数是否正确,QuickCheck 允许你描述函数的抽象特性并生成测试来验证这些特性。QuickCheck 现在已经是作为函数式编程语言测试的标配框架。

QuickCheck 的大致流程如下:

  • 生成随机数
  • 实现 check 函数
  • 缩小范围
  • 反复缩小范围

并行性 (Concurrent)

因为纯函数根本不需要访问共享的内存,而且根据其定义,纯函数也不会因副作用而进入竞争态(race condition), 所以也就不需要用锁来保护可变状态,也就不会出现死锁,Haskell 语言在一些需要高性能地处理并发问题的场景能够很好的解决问题的原因也是因为这个。

高阶函数

前面说到了诸多函数式编程的特性,但是我们实际开发中,很难保证自己写的函数是没有副作用的,尤其是客户端业务开发,上面副作用中提到的网络请求,磁盘写入,UI 操作等,可以说我们的代码中大部分的函数都包含了这些副作用操作。如果要用函数式编程来写业务代码,那还能干活么?

class ViewController: UIViewController {
    var color:UIColor

    func refresh(color: UIColor) {
        setColor(color: color)
        updateUI(color: color)
    }
    
    func setColor(color: UIColor) {
        self.color = color
    }
    func updateUI(color: UIColor) {
        self.view.backgroundColor = color
    }
}

上面这个例子中,refresh, setColor, updateUI 都不是纯函数,但是我们可以通过高阶函数,最小化副作用的影响:

    func bind(color: UIColor, _ fn1:(UIColor) -> (), _ fn2:(UIColor) -> ()) {
        fn1(color)
        fn2(color)
    }

    var refresh = bind(color, setColor, updateUI)
    refresh(color)

通过声明一个高阶函数 bind,接收外部传入的另外两个函数,这样这个 bind 函数就变成了纯函数,把副作用收敛在了 setColor 和 updateUI 中。

上面这个例子可能不是很实用,再举一个比较常见的例子,我们实现一个节流函数 throttle, 这个函数的作用是防止快速连续点击按钮时造成不在预期内的影响。

function onClick() {
    console.log("onclick")
}

button.addEventListener('click', onClick)

上面是原始代码,此时如果想实现节流效果,只需要实现一个 throttle 函数,这个函数接收真正执行点击事件的函数:

function throttle(fn, wait = 500) {
    let timer;
    return function (...args) {
        if (timer == null) { 
            timer = setTimeout(() => timer = null, wait)
            return fn.apply(this, args);
        }
    }
}

function onClick() {
    console.log("onclick")
}

button.addEventListener('click', throttle(onClick))

柯里化(Currying)

这个词得名于逻辑学家 Haskell Curry(注2),意思是将多参数函数转化为多个单参数函数叠加的过程。

func add1(_ x: Int, _ y: Int) -> Int { 
    return x+y
}

func add2(_ x: Int) -> ((Int) -> Int) {
    return { y in 
        return x+y
     }
}

add1(1, 2) // 3
add2(1)(2) // 3

所以柯里化的作用是什么?在后面的例子里会说明。

Swift 与函数式编程

世界上最纯粹的函数式编程语言非 Haskell 莫属,但是由于我国程序开发的起步和热⻔相对西方世界要晚一些,使用 Haskell 的开发者可谓寥寥无几,因此 Haskell 在国内的影响力也十分有限。对于国内的不少开发者,特别是 iOS / OS X 的开发者来说,Swift 可能是我们第一次真正有机会去接触和使用的一⻔函数式特性语言。相比于很多已有的函数式编程语言,Swift 在语法上更加优雅灵活,语言本身也遵循了函数式的设计模式。作为函数式编程的入⻔语言,可以说Swift 是非常理想的选择。

函数式编程:Objective-C vs Swift

一等公民的函数

Objective-C 中也有一等函数,或者说是 block,但是在 Objective-C 中使用 block 十分繁琐。一是因为语法问题:block 的声明和 block 的类型写法与 Swift 的对应部分相比要麻烦很多。

此外,Objective-C 中的 selector 实际只是一个字符串,也就是说你拿到一个 SEL 类型并不能直接当方法调用他,还需要利用 runtime 去做真实的调用。诚然通过一定的封装最终能够达到同样的效果,但是这样很不优雅并且容易出错。

func foo() {
}

let bar = self.foo;
bar();

在 swift 中可以很方便的把一个函数赋值给一个常量,这个在 Objective-C 中是做不到的。

高阶函数

Swift 标准库的容器类中自带了包括 map, reduce 等在内的高阶函数.

泛型

- (nullable id)btd_reduce:(nullable id)initialValue reducer:(_Nullable id(^)(_Nullable id preValue, ObjectType obj))block;
public func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, Element) throws -> Result) rethrows -> Result

上面两个方法一个是分类中自定义的高阶函数,一个是 swift 中的高阶函数,前者只能用有限度的泛型,因此返回值里只能用 id 类型,实际使用返回值的地方还需要做一次类型转换。 而后者可以通过参数的泛型约束定义返回值的类型,使用时代码会简洁很多。 同时在编译器自动补全方面,泛型的支持也能够提供很大的便利。

不可变数据

Swift中声明变量和不变量:

// 变量
var mutable
// 不变量
let immutable = 1

Swift实现一个不可变类的例子:

struct Person {
    let name:String
    let interests:[String]
}
  • 结构体(struct)是final的,即不可被继承;
  • let 声明的实例变量,保证了类初始化之后,实例变量无法再被改变;
  • struct 是值类型,将一个struct赋给另外一个变量,其实是拷贝了对像,将拷贝的对象赋值给另一个变量。

Swift中实现一个不可变的类的方法是:声明一个结构体(struct),并将该结构体的所有实例变量以let开头声明为不变量。在不变性这方面,枚举具有和结构体相同的特性。所以,上面例子中的结构体在合适的场景下,也可以被枚举类型替换。

struct PersonA {
    let name:String
    let interests:[String]
}

struct PersonB {
    var name:String
    var interests:[String]
}

var person1 = PersonA(name: "Joey", interests: ["Food"])
person1.name = "Chandler" // 不允许

let person2 = PersonA(name: "Joey", interests: ["Food"])
person2.name = "Chandler" // 不允许

var person3 = PersonB(name: "Joey", interests: ["Food"])
person3.name = "Chandler" // 允许

let person4 = PersonB(name: "Joey", interests: ["Food"])
person4.name = "Chandler" // 不允许

惰性求值

惰性求值: 在使用惰性计算时,表达式不在它被绑定到变量之后就立即求值,而是在该值被取用的时候求值。

Swift 默认严格求值, 也提供了相关的惰性求值的机制:

let array = [1,2,4,5,3,7]
let selement = array.map({$0 * 2})[3]
let element = array.lazy.map({$0 * 2 })[3]

上面代码中, 计算出 selement 花了 7 次, 计算 element 花了 2 次

  • 对于 7:将 array 每个元素分别取出 * 2,然后在这些数据中取出第 4 个元素, 6 + 1 = 7
  • 对于 2:因为是惰性求值,会先从 array 取出第 4 个元素, 然后 *2 , 1 + 1 = 2

柯里化

有一个银行账户类:

class BankAccount {
    var balance: Double = 0.0

    func deposit(_ amount: Double) {
        balance += amount
    }
}

我们可以通过这样调用去存 100 块:

let account = BankAccount()
account.deposit(100) // balance is now 100

也可以这样:

let depositor = BankAccount.deposit(_:)
depositor(account)(100) // balance is now 200

这两行代码和上面是等价的,BankAccount.deposit(_:)把成员函数赋值给了 depositor 变量,depositor 的函数签名:

let depositor: BankAccount -> (Double) -> ()

也就是说, Swift 中的实例方法,其实是一个类方法部分柯里化后的返回值。 利用这个特性,我们可以写一些设计代码,来简化开发,比如说,UIControl 体用了 target-action 的 API 来处理事件响应,但是传入一个 #selector 在 Swift 中写起来也太不优雅了,我们可以通过下面这个方案来简化调用:

protocol TargetAction {
    func performAction()
}

struct TargetActionWrapper<T: AnyObject> : TargetAction {
    weak var target: T?
    let action: (T) -> () -> ()

    func performAction() -> () {
        if let t = target {
            action(t)()
        }
    }
}

enum ControlEvent {
    case touchUpInside
    case valueChanged
    // ...
}

class Control {
    var actions = [ControlEvent: TargetAction]()

    func setTarget<T: AnyObject>(_ target: T, action: @escaping (T) -> () -> (), controlEvent: ControlEvent) {
        actions[controlEvent] = TargetActionWrapper(target: target, action: action)
    }

    func removeTargetForControlEvent(controlEvent: ControlEvent) {
        actions[controlEvent] = nil
    }

    func performActionForControlEvent(controlEvent: ControlEvent) {
        actions[controlEvent]?.performAction()
    }
}
Usage:
class MyViewController {
    let button = Control()

    func viewDidLoad() {
        button.setTarget(self, action: MyViewController.onButtonTap, controlEvent: .touchUpInside)
    }

    func onButtonTap() {
        print("Button was tapped")
    }
}

柯里化还有其他的好处,不过这里不一一详述了:

  • 延迟计算。
  • 参数复用。当在多次调用同一个函数,并且传递的参数绝大多数是相同的,那么该函数可能是一个很好的柯里化候选。
  • 动态创建函数。

函数响应式编程(Functional Reactive Programming)

苹果在 2019 年的 WWDC 发布了 SwiftUI 和 Combine, 这两个框架能够让程序员通过函数响应式编程 (Functional reactive programming, FRP)的方式来组织 UI 代码与逻辑代码。包括我们熟知的 ReactiveObjC 和 RxSwift 也都是 FRP 框架。

那么为什么是函数式+响应式呢,这两种编程范式结合在一起后,摩擦出了怎样的火花?

为什么函数式+响应式

上面提到,在纯函数是编程中,是不存在副作用的。但是,在很多软件,尤其是带 UI 的软件中,副作用是不可避免的,因为任何的 UI 操作一定是有副作用的。

想要用函数式编程,但是又得接受副作用,函数响应式编程就是一个比较好的解决方案。

响应式变成中一个很重要的概念就是事件流,通俗一点说就是,我们可以”不断地“获得某一个指定数据的值。

举例来说,下图表示一个鼠标在不断移动时产生的坐标事件流:

x = <mouse-x>;
y = <mouse-y>;

上面的伪代码表示:在任意时间 x 和 y 都能获取到鼠标的坐标值,这个在响应式编程中很容易做到,框架一般都会提供这类 API 让你观察某一个事件流。通过这种方式,响应式编程消除了可变值。于是我们可以定义下面这样一组函数,来获取坐标值 +- 16 的 4 个点。

minX = x - 16;
minY = y - 16;
maxX = x + 16;
maxY = y + 16;

接下来调用下面这个长方形函数,就能持续不断获取到以鼠标的坐标值为中心点一个 32x32 的长方形。

rectangle(minX, minY, maxX, maxY)

结合响应式编程,我们可以把一些函数式编程中遇到的副作用消除,在利用了函数式编程特性的同时,也实现了需求。这就是为什么函数式和响应式编程能够在一起配合的原因。

Combine

通过对事件处理的操作进行组合 (combine) ,来对异步事件进行自定义处理 (这也正是 Combine 框架的名字的由来)。Combine 提供了一组声明式的Swift API,来处理随时间变化的值。这些值可以代表用户界面的事件,网络的响应,计划好的事件,或者很多其他类型的异步数据。

在 Combine 诞生之前,有一个名为 RxSwift 的开源库已经很方便的让我们用 Swift 写 FRP 的代码了,为什么苹果还要弄这么一个 Combine 出来呢, Combine 和 RxSwift 相比又有什么优势?

Combine 与 RxSwift

  1. 两者同样作为函数响应式编程框架,由于遵循的是同样的编程范式,两者在实现原理上其实并没有什么不同,publisher, operator, subscriber 这些 combine 中的概念,在 RxSwift 中都能找到一一对应的关系;
  2. RxSwift 是个第三方库,而 Combine iOS 13 上的系统使用,在这一点上,RxSwift 显然有巨大的优势,苹果推出 Combine 这个库,相当于认可了函数响应式编程范式,短时间内应该会吸引更多的人去使用 RxSwfit, 不过从长期来说,Combine 毕竟是亲儿子,随着时间推移,大家能兼容的最小系统版本提升到 iOS 13(希望真的有那么一天)时,肯定会有越来越多的开发者转而使用 Combine;
  3. 责任
    • Combine 需要作为 Swift 语言的推广,让大家看到这门语言与 Apple生态的结合究竟能够产生怎样的优势。Swift 语言虽然是开源项目,但毫无疑问 Apple依然主导着这门语言的发展方向。Swift 5.1 中许多特性,都是为了能更简洁地使用 SwiftUI和Combine 而新增的。相比于其他竞品如 Flutter, RN,由于编程语言上的完全控制,SwiftUI 和 Combine 在使用上来说都比其他方案更加简明,可读性也更高。这对于吸引新人加入到 Swift 开发生态圈至关重要。
    • 其次,这两个框架还担负着 Apple 平台编程范式转变的重要任务。越来越多的案例证明,声明式和响应式可以有效加速进度,并减少开发者出错的可能性。而 Objective-C 积重难返,想要承担起这样的转变,可能需要花费更多的时间和精力。在如今 React Native、Flutter 以及微信小程序的 “围杀” 下,如何为开发者提供更好的创作环境,持续地吸引开发者留在自己的平台,是 Apple 不得不认真考虑的问题。

结语

那么,函数式编程到底是什么?许多人 (错误地) 认为函数式编程只是使用像 map 与 filter 这样的高阶函数进行编程,这可能有点管中窥豹,只⻅一斑。

Swift 在支持函数式编程上做了很多的变化了改进,就像之前的 Swift 5.1 上有诸多特性都是为了更简洁地使用 SwiftUI 和 Combine 而新增的。足以说明苹果对这种编程范式的重视。

虽然函数式编程并不是 Swift 编程的唯一方式,但掌握函数式编程,不论你使用那种语言,这件工具都会让你成为一个更好的开发者。 想更好地磨练函数式编程技能,一种方式是学习 Haskell。其实函数式语言还有很多种,比如F#,OCaml, Standard ML, Scala, 以及 Racket。无论是哪种语言,作为对 Swift 语言的学习补充都是不错的选择,但 Haskell 是最能考验编程思想的语言,随着对 Haskell 理解的深入,平时编写代码的方式也会有所改变。

- 注1:冯·诺伊曼结构这个词出自约翰·冯·诺伊曼的论文:First Draft of a Report on the EDVAC [2], 于1945年6月30日。

- 注2:Haskell Brook Curry——美国数学家和逻辑学家,一位在数理逻辑和计算机科学历史上里程碑式的存在,其名声不如阿兰·图灵那么响亮,其影响不如库尔特·哥德尔那么广泛,但是在真实的历史中,Curry对的人类的贡献完全可以和前者比肩。如果说图灵机是现代计算机程序设计语言的基本模型,那么可计算函数就是和图灵机等价的另一种模型。组合逻辑,是Curry穷毕生精力的研究成果,这种以函数为基本元素的逻辑系统现今已成为理论计算机科学研究中一颗璀璨的明星。可能你还不知道,Curry是世界上唯一一位数学家、逻辑学家,他的姓名从头到尾全部成为计算机语言的名称:Haskell,当下最流行的函数式编程语言;Brook,是斯坦福大学开发的流处理编程语言,面向图形处理和并行计算,其设计依据ANSI C标准;Curry是一种新型的语言——混合了函数式和逻辑式两种范式的编程语言,其母体则是Haskell。Curry在某种意义上是Haskell的超集。哈斯凯尔·布鲁克·柯里,他的著作连同他的名字都成为人类智慧的象征。

- 注3:艾兹赫尔·韦伯·戴克斯特拉(荷兰语:Edsger Wybe Dijkstra,荷兰语发音:[ˈɛtsxər ˈʋibə ˈdɛikstra],1930年5月11日-2002年8月6日),又译艾兹赫尔·韦伯·迪杰斯特拉,生于荷兰鹿特丹,计算机科学家,是荷兰第一位以程式为专业的科学家。[1]曾在1972年获得图灵奖,之后,他还获得1974年AFIPS Harry Goode Memorial Award、1989年ACM SIGCSE计算机科学教育教学杰出贡献奖。 2002年,在他去世前不久,艾兹赫尔获得了ACM PODC(分布式计算原理)最具影响力论文奖,以表彰他在分布式领域中关于程序计算自稳定的贡献。为了纪念他,这个每年一度奖项也在此后被更名为“Dijkstra奖”。 他曾经提出“GOTO有害论”,信号量和PV原语,解决了有趣的“哲学家就餐问题”。

References