Kotlin 函数式编程

(Kotlin Functional Programming) 

   陈光剑


1. 函数式概述 6

1.1. 函数式简史 6

1.2. 函数式编程语言家族 7

1.2.1. 概述 7

1.2.2. 函数式编程语言介绍 8

1.3. 函数式编程的特征 10

1.3.1. 函数是"第一等公民"(First-class and higher-order functions) 10

1.3.2. 没有"副作用":纯函数 11

1.3.3. 不修改状态 11

1.3.4. 引用透明(Referential transparency) 11

1.3.5. 只用"表达式",不用"语句" 11

1.4. 函数式编程的优势 12

1.5. 怎样进行函数式思维 12

1.5.1. 将运算视为函数的计算 12

1.5.2. 把运算过程写成嵌套函数调用 12

1.5.3. 真正要掌握的是函数式的思维方式 12

1.6. 案例研究:以集合遍历为例从命令式重构成函数式 13

1.7. 小结 13

2. 编程范式的转变 13

2.1. 命令式 13

2.1.1. 什么是命令式编程 13

2.1.2. 从汇编到 C 语言 13

2.2. 声明式 13

2.2.1. 什么是声明式编程 13

2.2.2. SQL 编程语言 13

2.2.3. 函数式编程风格 13

2.3. 编程的本质 13

2.3.1. 命令式:程序=数据结构+算法 13

2.3.2. 函数式:程序= 函数(函数(...)) 13

2.4. 函数式接口:从面向对象到函数式 14

2.5. 状态的改变 14

2.6. 映射的本质 14

2.7. 函数是一等公民 14

2.8. 从递归到递归 14

2.8.1. 函数调用与 Stack 14

2.9. 从函数到函数 14

2.9.1. 范畴论 14

2.9.2. 函子 14

2.10. 闭包 14

2.11. Lambda表达式 14

2.12. 柯里化 14

2.13. 案例研究:map 和 reduce的使用 15

2.14. 小结 15

3. λ演算 15

3.1. 什么是λ 演算 15

3.1.1. 判定性问题 15

3.1.2. 可计算函数 15

3.2. BNF 范式表达语法 15

3.2.1. 什么是 BNF 范式 15

3.2.2. λ 演算的 BNF 表达式 15

3.3. 图灵机与λ 演算 15

3.3.1. 图灵机与图灵完备 15

3.3.2. λ 演算vs. 图灵机 15

3.4. λ 演算的归约策略 17

3.4.1. α规约 17

3.4.2. β-归约 17

3.4.3. Applicative 17

3.5. 并行与并发 18

3.5.1. 并行是什么 18

3.5.2. 并发是什么 18

3.5.3. 并发程序的设计 18

3.6. 案例研究:使用λ 演算定义自然数Church 整数 18

3.7. 小结 20

4. Y 组合子 21

4.1. 什么是 Y 组合子 21

4.1.1. 函数不动点 21

4.1.2. 递归研究 21

4.2. Y 组合子推导 21

4.3. 案例研究:多种编程语言实现 Y 组合子 21

4.4. 小结 21

5. 函数式编程中的设计模式 21

5.1. 设计模式是什么 21

5.2. 从OOP设计模式到函数式 21

5.2.1. 从迭代子到高阶函数 21

5.2.2. 从策略模式到高阶函数 21

5.2.3. 从模板方法到高阶函数 21

5.3. OOP 与函数式的设计模式对比 21

5.4. 代码的表面积与体积 21

5.5. 案例研究:递归遍历树状表格中的元素 21

5.6. 小结 21

6. 函数式元编程 21

6.1. 什么是元编程 22

6.1.1. 简介 22

6.1.2. 元编程与反射 22

6.2. 类型系统与元编程 23

6.2.1. 什么是类型系统 23

6.2.2. 类型系统与程序设计语言 23

6.3. 运行时元编程与编译时元编程 23

6.3.1. 泛型与注解 23

6.3.2. 反射 23

6.4. 案例研究:一个简单的元编程的例子 23

6.5. 小结 23

7. 抽象到极致 23

7.1. 抽象之抽象:抽象到极致 25

7.1.1. 抽象是函数式编程最重要的概念 25

7.1.2. 将抽象推到极致 25

7.2. 函数式金字塔 25

7.3. 案例研究:怎样把产品需求一步步“编译”成系统的? 25

7.4. 小结 25

8. 领域特定语言 DSL 25

8.1. DSL 简介 25

8.1.1. 什么是DSL 25

8.1.2. 通用程序设计语言与 DSL 25

8.2. Kotlin 生态中的DSL 案例 25

8.2.1. Gradle  构建脚本 25

8.2.2. Spring Bean的新玩法 25

8.2.3. 简化 Android 开发的Anko 25

8.3. 案例研究:用 Kotlin 实现一个后端Ajax 请求的 DSL 25

8.4. 小结 25

9. 函子与单子 25

9.1. 函子简介 25

9.1.1. 什么是函子 25

9.1.2. 函子的定义 25

9.1.3. 函子的例子 25

9.2. 单子简介 26

9.2.1. 单子是什么 26

9.2.2. 组织有序操作 26

9.2.3. 定义任意的控制流 26

9.3. 案例研究:构建一个表格应用解析器 26

9.4. 小结 26

1. 函数式概述

1.1. 函数式简史

• 起源与基础:λ演算(lambda calculus)

• "函数式编程"是一种"编程范式"(programming paradigm),也就是如何编写程序的方法论。

• 它属于"结构化编程"的一种,主要思想是把运算过程尽量写成一系列嵌套的函数调用。

– 在计算机科学中,函数式编程是一种编程范式。函数式将计算视为数学函数的调用,避免了状态变化和可变数据。它是一种声明式编程范式,函数式编程用表达式或声明,而不是语句。

在函数代码中,函数的输出值只取决于它的参数,即不依赖于函数输入的状态变化,可以使理解程序变得更容易,这是开发函数编程的关键动机之一。因此相同入参的函数,总是返回相同的结果。这与命令式编程不同,命令式编程除了函数的参数外,全局程序状态还会影响函数的结果值。

函数式编程起源于lambda演算,lambda演算是20世纪30年代开发的一个形式化系统,用于研究可计算性、可判定问题(Entscheidungsproblem)、函数定义、函数应用和递归。许多函数式编程语言都可以看作是lambda演算的发展丰富。

与函数式不同,命令式编程使用源代码中的语句更改状态。最简单的例子是赋值。命令式编程有子程序,但这些不是数学函数。它们可能有副作用,可能会改变程序的状态,允许函数没有返回值。因此,它们缺乏引用透明性,也就是说,相同的语言表达式可以根据执行程序的状态在不同的时间产生不同的值。

函数式编程在一定程度上只是被学术界重视。然而,支持函数式编程的编程语言,已经慢慢火爆起来,在工业中得到了越来越多的应用,包括常用Lisp、Scheme、 Clojure、Wolfram、 Racket、Erlang、OCaml、Haskell 和 F# 等。

JavaScript是世界上分布最广泛的语言之一,除了命令式和面向对象的范式外,还具有动态类型函数语言的特性。函数式编程在某些特定领域也取得成功,比如统计中的R、J, 财务分析中的K和Q以及XML的XQuery/XSLT。特定于领域的声明式语言,如SQL和Lex/Yacc。

现代编程语言基本都是多范式的。比如使用Perl、PHP、C++ 11和Kotlin等。一个有趣的例子是Scala——它经常使用函数式编写,但是由于副作用和可变状态的存在,使得它处于命令式语言和函数式语言之间的灰色区域。

1.2. 函数式编程语言家族

1.2.1. 概述

– 如果我们关注各种语言的发展情况就会发现,所有的主流语言都在进行函数式方面的 扩充。早走一步的Groovy 已经具备了丰富的函数式特性,包括像“记忆”(memoization,指运行时自动缓存函数返回值的能力)这样的高级特性在内。随着lambda 块(也就是高阶函数)被纳入Java 8,Java 语言也终于披挂上函数式的武器。JavaScript,这种也许算得上使用最为广泛的语言,本身就拥有不少函数式特性。就连最老成持重的C++ 语言,也在2011 年版的语言标准里增加了lambda 块。

– 在计算机科学短短的发展历史上,有时候会从技术主流分出一些枝杈,有源于实务界的,也有源于学术界的。例如在20 世纪90 年代个人电脑大发展的时期,第四代编程 语言(4GL)也出现了爆发式的流行,涌现了dBASE、Clipper、FoxPro、Paradox 等不可胜数的新语言。这些语言的卖点之一是比C、Pascal 等第三代语言(3GL)更高层次的抽象。

换言之,4GL 下的一行命令,3GL 可能要用很多行才写得出来,因为4GL 自带了更丰富的编程环境。像从磁盘读取流行的数据库格式这样的功能,4GL 天生就具备,并不需要使用者特意去实现。

函数式编程也是这样一根横生出来的枝杈,是学术界那些乐于为新思路和新范式寻找 表达手段的计算机科学家们的发明。分出来的枝杈偶尔会重新汇入主流,函数式编程当前正好是这种情况。函数式语言不仅在Java 虚拟机(JVM)平台上迅速地崭露头角,例如最有代表性的Scala 和Clojure 语言,.NET 平台也不例外,F# 已经是堂堂正正的平台一员。那么,为什么所有的平台都在拥抱函数式编程呢?

20 世纪80 年代早期,有个编程环境叫作Pecan Pascal。Pecan Pascal的独门绝技是可以在Apple ][ 和IBM PC 上运行相同的Pascal 代码。为了做到这一点,Pecan 的工程师使用了神秘的“字节码”(bytecode)。在编译的时候,开发者写下的Pascal源代码会被编译成这种在“虚拟机”上执行的“字节码”,而“虚拟机”在每一种运行平台上都有专门的原生实现。Pecan Pascal 用起来让人痛不欲生。就算最简单的编程习题,编译出来的代码都慢得无法忍受。当时的硬件水平还没有准备好迎接这样的挑战。Pecan Pascal 被淘汰了,但它的架构我们都很熟悉。

十年之后Sun 发布了采用同样设 计的Java,自动垃圾收集、跨平台的特性使得它称霸编程界二十余年。

人生苦短,把时间花在更高层次的抽象上,多考虑怎样解决复杂的业务场景,少去费心复杂的底层运作。Java 在很大程序上,把程序员从人工管理内存中解放出来(当然,很多时候,你也不得不 JVM 性能调优,就像打地鼠一样,有些问题始终需要人为地来解决的)。

函数式编程语言让我们用高阶抽象从容取代基本的控制结构,也有着同样的意义。将琐碎的细节交托给运行时,令繁冗的实现化作轻巧,这样的例子比比皆是。

1.2.2. 函数式编程语言介绍

1.2.2.1. Lisp

• 1958年,约翰·麦卡锡(John McCarthy)在麻省理工学院(MIT)工作时发明发明LISP。用于人工智能领域。Lisp是仅次于Fortran的第二古老的高级编程语言,并且从早期开始就发生了很大变化,并且在其历史上存在许多方言。 Lisp 有两个特征,函数式编程,和它是一门面向语言的语言。 今天,最广为人知的通用Lisp方言是Common Lisp和Scheme。 Emacs,G2,AutoCad,Igor Engraver,Yahoo Store 等大量成功的应用建立在Lisp语言之上。

• Common LISP 特性

– 它与机器无关

– 它使用迭代设计方法,并且易于扩展。

– 它允许动态更新程序。

– 它提供高级调试。

– 它提供了高级的面向对象编程。

– 它提供了一个方便的宏系统。

– 它提供广泛的数据类型,如对象,结构,列表,向量,可调整数组,哈希表和符号。

– 它是基于表达的。

– 它提供了面向对象的条件系统。

– 它提供了一个完整的I / O库。

– 它提供了广泛的控制结构。

• Lisp 实现 Fibonacci 数列函数:

(defun fib (n)
  (if (<= n 1)
      1
      (+ (fib (- n 1))
         (fib (- n 2)))))

(fib 10)
89

1.2.2.2. Clojure

为什么Clojure ? 为什么还要写另一种编程语言? 基本上是因为我想要一个Lisp。函数式编程。与已建立的JVM平台生态共生。函数式编程是一件好事。不可变数据 + 一等公民函数。

Clojure是一种动态类型的函数式语言。所有数据结构都是不可变的和持久的,支持递归。

Clojure is a functional programming language. It provides the tools to avoid mutable state, provides functions as first-class objects, and emphasizes recursive iteration instead of side-effect based looping. Clojure is impure, in that it doesn’t force your program to be referentially transparent, and doesn’t strive for 'provable' programs. The philosophy behind Clojure is that most parts of most programs should be functional, and that programs that are more functional are more robust.

user=> (def hello (fn [] "Hello World"))
#'user/hello
user=> (hello)
"Hello World"

1.2.2.3. Haskell

An advanced, purely functional programming language

• Haskell is lazy

That means that unless specifically told otherwise, Haskell won't execute functions and calculate things until it's really forced to show you a result.

• Haskell is statically typed

When you compile your program, the compiler knows which piece of code is a number, which is a string and so on. That means that a lot of possible errors are caught at compile time. If you try to add together a number and a string, the compiler will whine at you. Haskell uses a very good type system that has type inference. That means that you don't have to explicitly label every piece of code with a type because the type system can intelligently figure out a lot about it.

• Haskell is elegant and concise

Because it uses a lot of high level concepts, Haskell programs are usually shorter than their imperative equivalents. And shorter programs are easier to maintain than longer ones and have less bugs.

环境安装

Run the following in your terminal (as a user other than root), then follow the onscreen instructions.

curl https://get-ghcup.haskell.org -sSf | sh

教程

Learn You a Haskell for Great Good!: http://learnyouahaskell.com/chapters

1.2.2.4. Erlang

简介

Erlang是一个结构化,动态类型编程语言,内建并行计算支持。

Erlang是一种通用的面向并发的编程语言,它由瑞典电信设备制造商爱立信所辖的CS-Lab开发,目的是创造一种可以应对大规模并发活动的编程语言和运行环境。Erlang问世于1987年,经过十年的发展,于1998年发布开源版本。

Erlang是运行于虚拟机的解释性语言,但是现在也包含有乌普萨拉大学高性能Erlang计划(HiPE)开发的本地代码编译器,自R11B-4版本开始,Erlang也开始支持脚本式解释器。在编程范型上,Erlang属于多重范型编程语言,涵盖函数式、并发式及分布式。

教程

Getting Started with Erlang User's Guide:   http://erlang.org/doc/gettingstarted/usersguide.html

1.2.2.5. JavaScript

1.2.2.6. Python

1.2.2.7. Java 8

1.2.2.8. Scala

1.2.2.9. Kotlin

1.3. 函数式编程的特征

1.3.1. 函数是"第一等公民"(First-class and higher-order functions)

– 所谓"第一等公民"(first class),指的是函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值。

1.3.2. 没有"副作用":纯函数

纯函数(Pure functions,no side effects (memory or I/O))。

所谓"副作用"(side effect),指的是函数内部与外部互动(最典型的情况,就是修改全局变量的值),产生运算以外的其他结果。

函数式编程强调没有"副作用",calling the pure function again with the same arguments returns the same result.

1.3.3. 不修改状态

– 函数式编程只是返回新的值,不修改系统变量。因此,不修改变量,也是它的一个重要特点。

– 在其他类型的语言中,变量往往用来保存"状态"(state)。不修改变量,意味着状态不能保存在变量中。函数式编程使用参数保存状态,最好的例子就是递归。

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

1.3.4. 引用透明(Referential transparency)

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

– 函数的返回值与系统状态有关,不同的状态之下,返回值是不一样的。这叫"引用不透明",很不利于观察和理解程序的行为。

1.3.5. 只用"表达式",不用"语句"

– "表达式"(expression)是一个单纯的运算过程,总是有返回值;"语句"(statement)是执行某种操作,没有返回值。函数式编程要求,只使用表达式,不使用语句。也就是说,每一步都是单纯的运算,而且都有返回值。

原因是函数式编程的开发动机,一开始就是为了处理运算(computation),不考虑系统的读写(I/O)。"语句"属于对系统的读写操作,所以就被排斥在外。

当然,实际应用中,不做I/O是不可能的。因此,编程过程中,函数式编程只要求把I/O限制到最小,不要有不必要的读写行为,保持计算过程的单纯性。

1.4. 函数式编程的优势

1.5. 怎样进行函数式思维

1.5.1. 将运算视为函数的计算

计算机,Computer

1.5.2. 把运算过程写成嵌套函数调用

1.5.3. 真正要掌握的是函数式的思维方式

• 越来越多的命令式语言都在增加函数式能力。Java等现代编程语言中出现了越来越多的函数式特性(Java 8 ),函数式编程的语法只是表象, 而我们真正需要掌握的是函数式编程的新思维。

函数式编程思想通过改换视角,让我们站在了另一个抽象层次上,把编程问题看得更加清晰。

设计模式和代码重用,在函数式语境下是怎样的表现形态?

• 函数式编程思维,脱离特定的语言特性,关注各种OOP语言的共同实践做法,展示如何通过函数式语言解决问题。例如,如何利用函数式语言,通过高阶函数、Lambda, DSL 等完成代码重用。

1.6. 案例研究:以集合遍历为例从命令式重构成函数式

1.7. 小结

2. 编程范式的转变

2.1. 命令式

2.1.1. 什么是命令式编程

2.1.2. 从汇编到 C 语言

2.2. 声明式

2.2.1. 什么是声明式编程

2.2.2. SQL 编程语言

2.2.3. 函数式编程风格

2.3. 编程的本质

2.3.1. 命令式:程序=数据结构+算法

数据结构

函数式的数据结构

面向对象编程的数据结构

算法

函数式的算法

面向对象编程中的”方法“

2.3.2. 函数式:程序= 函数(函数(...))

一切皆是映射,映射即流,流即函数。

函数就是来描述事物的运动变化的。

2.4. 函数式接口:从面向对象到函数式

2.5. 状态的改变

– 对比OOP,FP的核心就是一切都是函数,也就是说”活“的不再是对象,而是函数。

– 函数本质是x到f(x)的变换,所以FP的元素就两种,一种是函数(”pure function“的概念,表现为命名句柄或表达式,所以独立性->并发性),一种是状态(immutable,因为只要发生变换,就通过函数表现)。

– 函数的独立性,导致了我们没法记录函数内部运行的状态,怎么解决呢,把状态放在参数里呗,然后用递归来搞定。

2.6. 映射的本质

– 一切皆是映射

2.7. 函数是一等公民

• 函数是一等公民, 函数可以当做参数传递给另外一个函数, 函数也可以作为另外一个函数的返回值。

2.8. 从递归到递归

2.8.1. 函数调用与 Stack

2.9. 从函数到函数

2.9.1. 范畴论

2.9.2. 函子

2.10. 闭包

2.11. Lambda表达式

2.12. 柯里化

• 表达式(1 + 2) * 3 - 4

– subtract(multiply(add(1,2), 3), 4)

– 链式调用:add(1,2).multiply(3).subtract(4)

2.13. 案例研究:map 和 reduce的使用

2.14. 小结

3. λ演算

3.1. 什么是λ 演算

3.1.1. 判定性问题

3.1.2. 可计算函数

λ演算,λ(Lambda(大写Λ,小写λ)演算是一套用于研究函数定义、函数应用和递归的形式系统。它由 Alonzo Church 和 Stephen Cole Kleene 在 20 世纪三十年代引入,Church 运用 lambda 演算在 1936 年给出 判定性问题 (Entscheidungsproblem) 的一个否定的答案。这种演算可以用来清晰地定义什么是一个可计算函数。关于两个 lambda 演算表达式是否等价的命题无法通过一个通用的算法来解决,这是不可判定性能够证明的头一个问题,甚至还在停机问题之先。

3.2. BNF 范式表达语法

3.2.1. 什么是 BNF 范式

3.2.2. λ 演算的 BNF 表达式

3.3. 图灵机与λ 演算

3.3.1. 图灵机与图灵完备

3.3.2. λ 演算vs. 图灵机

λ 演算可以被称为最小的通用程序设计语言。它包括一条变换规则 (变量替换) 和一条函数定义方式,λ演算之通用在于,任何一个可计算函数都能用这种形式来表达和求值。因而,它是等价于图灵机的。尽管如此,λ演算强调的是变换规则的运用,而非实现它们的具体机器。可以认为这是一种更接近软件而非硬件的方式。它一个数理逻辑形式系统,使用变量代入和置换来研究基于函数定义和应用的计算。希腊字母λ被用来在λ演算模型中表示将一个变量绑定在一个函数中。

λ演算可以是有类型的也可以是无类型的,仅仅当输入的的数据类型对于有类型的λ演算函数来说是可以接受的时,有类型的λ演算函数才能被使用。λ演算模型在数学,物理学,语言学和计算机科学等不同领域有着广泛的应用。它在编程语言的理论发展上起到了很重要的作用,并对函数式编程起到了很大的影响,甚至可以说函数式编程就是对λ演算模型的一种实现。同时,它也是范畴论的当前研究对象之一。

λ演算模型最初的形式系统在1935年被 Stephen Kleene 和 J. B. Rosser提出的Kleene–Rosser悖论证明为是前后矛盾的,接着,在1936年,Church单独出版了λ演算模型中的和纯计算有关的部分,也就是如今被称为的无类型λ演算。在1940年,他提出了一个弱化计算,但是逻辑自洽的形式系统,如今被称之为简单类型λ演算。

在20世纪60年代之前,λ演算和编程语言之间的关系被厘清之前,λ演算模型一直都仅仅是一个理论上的形式系统,多亏了Montague和其他的几位语言学家在自然语言的语义方面的研究,λ演算开始在语言学和计算机科学的研究中占有一席之地。

Peter Landin1965年在论文 A Correspondence between ALGOL 60 and Church's Lambda-notation中指出的一样,面向过程的程序设计语言在λ演算模型的体系中是可以理解的,因为它提供了基本的过程抽象和程序应用机制。

匿名函数

以Lisp中的乘方函数为例,它可以使用lambda表达式表达为:

(lambda (x) (* x x))

以上是一个可以用作头等函数的例子。在这个表达式中,lambda创造了一个匿名函数并给予了它一个参数列表(x),虽然它只有一个参数,而(* x x)则是函数的主体。这个函数在Haskell中的实现是完全一样的。匿名函数有时候也被叫做lambda表达式。

再举个例子,Pascal和其它的命令式语言 长期以来都支持将通过函数指针的机制来将子程序 作为参数传递到其它子程序里面去。然而,函数指针并不是 函数成为头等函数类型的充分条件,因为一个头等数据类型必须要能够在运行时创建出一个它的实例。而支持运行时创建函数的语言有Smalltalk,Javascript,和最近的Scala,Eiffel,C#和C++11等。

3.4. λ 演算归约策略

3.4.1. α规约

3.4.2. β-归约

3.4.3. Applicative 

在编程语言的理论研究中,求值策略(Evaluation strategy)是一组用来确定程序设计语言中的表达式求值的规则。求值策略主要规定了在什么时候和用什么样的顺序给函数的实际参数求值,何时把参数代换入函数内,和用怎样的形式来进行代换。通常,人们使用λ演算模型中的归约策略来建模求值策略。

无论一个表达式是否为标准状态,将这个这个表达式化为标准型所需要的工作量很大程度上依赖于归约策略的使用。而归约策略的不同又和函数式编程中的及早求值还有惰性求值之间的不同有关。

β-归约 (β-reduction)

任何参数在任何时候都可以被归约,其实就是没有任何的归约策略,天知道会发生什么。

应用次序 (Applicative order)

最右边,最内部的表达式总是首先被归约,直观上可以知道,这意味着函数的参数总是在函数调用之前就被归约了。应用次序总是企图用标准形式去调用函数,即便在很多时候这是不可能的。 大多数的程序设计语言(包括Lisp,ML和命令式语言C和Java等)都被描述为严格类型语言,意思是使用了不正确形式参数的函数是形式不正确的。它们在实际上就是使用了应用次序和传值调用归约,但通常被成为及早求值策略。

3.正常次序 (Normal order) 最左边,最外部的表达式总是首先被归约,这也就意味着无论什么时候,参数都是再被归约之前就被替换进了抽象的函数体里面了。

4.传名调用 (Call by name) 和正常次序一样,但是不会在抽象的函数体中再进行归约,比如说,λx.(λx.x)x在这个策略中是正常形式, 虽然它包含了可归约的表达式(λx.x)x

5.传值调用 只有最外部的表达式被归约:一个表达式仅仅当它的右边已经被规约为一个值了才会被归约

6.传需求调用 “传需求调用”和传名调用类似,如果函数的实参被求值了,这个值就会被存储起来已备未来使用。它产生的结果和传名调用一样;但是如果函数的这个实参被调用了多次,那么传需求调用可以提高程序运行效率。它在现实语境中也被叫做惰性求值。

3.5. 并行与并发

3.5.1. 并行是什么

3.5.2. 并发是什么

3.5.3. 并发程序的设计

函数式编程在一开始就是面向并发处理的,这也得益于lambda的性质,lambda演算的Church-Rosser性质意味着归约(β归约)可以以任何顺序进行,甚至是并行来进行。这意味着各种不同的非确定性归约策略都是相近的。然而,lambda演算并不提供任何直接的并行结构。一个人可以添加像Futures结构体这样的并发结构体到lambda演算中去。相关的进程代数已经为了进程通信和并发而被研究了出来。

在λ-演算的基础上,发展起来的π-演算、χ-演算,成为近年来的并发程序的理论工具之一,许多经典的并发程序模型就是以π-演算为框架的。

3.6. 案例研究:使用λ 演算定义自然数Church 整数

在 lambda 演算中有许多方式都可以定义自然数,但最常见的还是Church 整数,下面是它们的定义:

0 = λ f. λ x. x

1 = λ f. λ x. f x

2 = λ f. λ x. f (f x)

3 = λ f. λ x. f (f (f x))

以此类推。直观地说,lambda 演算中的数字 n 就是一个把函数 f 作为参数并以 f 的 n 次幂为返回值的函数。换句话说,Church 整数是一个高阶函数 -- 以单一参数函数 f 为参数,返回另一个单一参数的函数。

(注意在 Church 原来的 lambda 演算中,lambda 表达式的形式参数在函数体中至少出现一次,这使得我们无法像上面那样定义 0) 在 Church 整数定义的基础上,我们可以定义一个后继函数,它以 n 为参数,返回 n + 1:

SUCC = λ n. λ f. λ x. f (n f x)

加法是这样定义的:

PLUS = λ m. λ n. λ f. λ x. m f (n f x)

PLUS 可以被看作以两个自然数为参数的函数,它返回的也是一个自然数。你可以试试验证

PLUS 2 3 与 5

是否等价。乘法可以这样定义:

MULT = λ m. λ n. m (PLUS n) 0,

即 m 乘以 n 等于在零的基础上 n 次加 m。另一种方式是

MULT = λ m. λ n. λ f. m (n f)

正整数 n 的前驱元 (predecessesor) PRED n = n - 1 要复杂一些:

PRED = λ n. λ f. λ x. n (λ g. λ h. h (g f)) (λ u. x) (λ u. u)

或者

PRED = λ n. n (λ g. λ k. (g 1) (λ u. PLUS (g k) 1) k) (λ l. 0) 0

注意 (g 1) (λ u. PLUS (g k) 1) k 表示的是,当 g(1) 是零时,表达式的值是 k,否则是 g(k) + 1。

逻辑与断言

习惯上,下述两个定义 (称为 Church 布尔值) 被用作 TRUE 和 FALSE 这样的布尔值:

TRUE = λ u. λ v. u

FALSE = λ u. λ v. v

断言是指返回布尔值的函数。最基本的一个断言 ISZERO,当且仅当其参数为零时返回真:

ISZERO = λ n. n (λ x. FALSE) TRUE

断言的运用与上述 TRUE 和 FALSE 的定义,使得 "if-then-else" 这类语句很容易用 lambda 演算写出。

递归

递归是一种以函数自身迭代自身变元的算法,一般是通过函数自身来定义函数的方式实现。表面看来 lambda 演算不允许递归,其实这是一种对递归的误解。考虑阶乘函数 f(n) 一般这样递归地定义:

f(n) = 1, 若 n = 0; n·f(n-1), 若 n>0.

λ语言示例

FACT = λ n. n (λ u. MULT n (FACT (PRED n))) 1

用 Y-组合子 在 λ语言 中合法地定义:

FACT = Y (λ g. λ n. n (λ u. MULT n (g (PRED n))) 1)

Y = λ f. ((λ x. f (x x)) (λ x. f (x x)))

3.7. 小结

4. Y 组合子

4.1. 什么是 Y 组合子

4.1.1. 函数不动点

4.1.2. 递归研究

4.2. Y 组合子推导

4.3. 案例研究:多种编程语言实现 Y 组合子

4.4. 小结

5. 函数式编程中的设计模式

5.1. 设计模式是什么

5.2. 从OOP设计模式到函数式

5.2.1. 从迭代子到高阶函数

5.2.2. 从策略模式到高阶函数

5.2.3. 从模板方法到高阶函数

5.3. OOP 与函数式的设计模式对比

5.4. 代码的表面积与体积

5.5. 案例研究:递归遍历树状表格中的元素

5.6. 小结

6. 函数式元编程

基于函数式语言的元编程系统,讨论元编程系统特别是同构系统的语言特点。从程序反射的角度分析元编程系统对程序设计语言在自我表示、自我分析和控制等方面的要求。以 MetaML 和 Template Haskell 为例论述在函数式语言中为了支持元编程需要扩展的机制,包括语法、语义、类型系统、安全的变量使用等,以及它们的实现方案、各方案的特点。最后总结一些元编程系统的共同点,并预测未来的发展趋势。

6.1. 什么是元编程

6.1.1. 简介

元编程(Metaprogramming)是指某类计算机程序的编写,这类计算机程序编写或者操纵其他程序(或者自身)作为它们的数据,或者在运行时完成部分本应在编译时完成的工作。很多情况下与手工编写全部代码相比工作效率更高。编写元程序的语言称之为元语言,被操作的语言称之为目标语言。一门语言同时也是自身的元语言的能力称之为反射。

6.1.2. 元编程与反射

反射是促进元编程的一种很有价值的语言特性。把编程语言自身作为头等对象(如Lisp或Rebol)也很有用。支持泛型编程的语言也使用元编程能力。

元编程通常有两种方式起作用。一种方式是通过应用程序接口(API)来暴露运行时引擎的内部信息。另一种方法是动态执行包含编程命令的字符串。因此,“程序能编写程序”。虽然两种方法都能用,但大多数方法主要靠其中一种。

最常用的元编程工具是编译器,把高级语言转换为汇编语言或机器语言。更灵活的方法是在程序中嵌入解释器直接处理程序数据。有一些实现例如为Object Pascal编写的RemObject's Pascal Script。

另一个很常用的元编程例子是lex和yacc,用来生成词法分析器和语法分析器。Yacc通常用作编译器的编译器,生成一个把高级语言转换为机器语言的工具。

quine是一种源代码等于输出的特殊的元程序。

面向语言的程序设计是一种强烈关注元编程的编程风格,通过领域特定语言来实现。

6.2. 类型系统与元编程

6.2.1. 什么是类型系统

6.2.2. 类型系统与程序设计语言

6.3. 运行时元编程与编译时元编程

6.3.1. 泛型与注解

6.3.2. 反射

6.4. 案例研究:一个简单的元编程的例子

6.5. 小结

7. 抽象到极致

什么是functional programming?

FP并没有明确的定义,只能通过个人(浅显的)理解来回答了:

函数式编程是指一种编程范式,其first-class-value是函数,并有如下properties:

1. 因为函数为first-class-value,所以函数可以当参数和返回值传递

(这就产生了lambda表达式/匿名函数)

2. immutable,也可叫purity或 referentially transparent

referentially transparent可由以下两条规则概括:

An expression e is referentially transparent if for all programs p, every occurrence of e in p can be replaced with the result of evaluating e without changing the result of evaluating p.

A function f is pure if the expression f(x) is referentially transparent for all referentially transparent x.

3. 支持函数式编程的语言(或称函数式语言)至少要实现/支持以上properties

(没有显式支持以上特性的语言,也可通过函数指针等来做非语言层面的支持)

(函数式编程源于lambda calculus,这点楼上说的很清楚了;也正是lamdba calculi 才使得其具有以上特性,不然就不可做规约与代换了)

其它的如Generalizing Functions,ADT,Pattern Matching,Lazy Evaluation,CPS...都可视为上面特性的扩展/附加,也是FP及type theory不断发展的产物,为了更加类型安全,为了更好的Functional Composition(函数/计算的组合)

到后面各语言引入各种Side Effect机制,也是为了更好的函数组合;category theory和abstract algebra里面的一些东西(如:Functor, Monad...)也被拿过来用于实现更好的FP(并且first-class-value 也慢慢转变为了type)

函数式编程的思想就是上面所说的Functional Composition。

7.1. 抽象之抽象:抽象到极致

7.1.1. 抽象是函数式编程最重要的概念

7.1.2. 将抽象推到极致

7.2. 函数式金字塔

7.3. 案例研究:怎样把产品需求一步步“编译”成系统的?

7.4. 小结

8. 领域特定语言 DSL

8.1. DSL 简介

8.1.1. 什么是DSL

8.1.2. 通用程序设计语言与 DSL

8.2. Kotlin 生态中的DSL 案例

8.2.1. Gradle  构建脚本

8.2.2. Spring Bean的新玩法

8.2.3. 简化 Android 开发的Anko

8.3. 案例研究:用 Kotlin 实现一个后端Ajax 请求的 DSL

8.4. 小结

9. 函子与单子

Kotlin 函数式编程(Kotlin Functional Programming)相关推荐

  1. 初识函数式编程(Functional Programming,FP)

    文章内容输出来源:拉勾教育 大前端高薪训练营 前言 函数式编程是种编程方式,它将电脑运算视为函数的计算.函数编程语言最重要的基础是λ演算(lambda calculus),而且λ演算的函数可以接受函数 ...

  2. 函数式编程(Functional Programming)

    函数式语言一直以来比较低调,直到最近由于并发计算编程瓶颈的出现,以及Haskell,Scala之类语言社区的大力鼓吹,它忽然变成了一种宗派.有人盲目的相信函数式编程能够奇迹般的解决并发计算的难题,而看 ...

  3. kotlin函数式编程_我最喜欢的Kotlin函数式编程示例

    kotlin函数式编程 by Marcin Moskala 通过Marcin Moskala One of the great things about Kotlin is that it suppo ...

  4. Kotlin函数式编程

    什么是函数式编程 函数式编程是于面向对象编程范式相齐名的一种编程方式,主要依赖于高阶函数返回的数据,这些高阶函数专用于处理各种集合,可方便的联合多个同类函数构建链式操作以创建复杂的计算行为.在Kotl ...

  5. 【 Kotlin 脚本编程Kotlin Scripting 】Linux Shell 脚本与Kotlin Scripting

    Linux 英文解释为 Linux is not Unix. Linux 内核最初只是由芬兰人林纳斯·托瓦兹(Linus Torvalds)在赫尔辛基大学上学时出于个人爱好而编写的. Linux 是一 ...

  6. Functional Programming函数式编程

    概论 在过去的近十年的时间里,面向对象编程大行其道.以至于在大学的教育里,老师也只会教给我们两种编程模型,面向过程和面向对象.孰不知,在面向对象产生之前,在面向对象思想产生之前,函数式编程已经有了数十 ...

  7. JavaScript 函数式编程范式

    目录 一. 函数式编程范式 1.1 编程范式定义 1.2 函数式编程范式特点 1.3 函数式编程范式基本概念 1.4 章节学习指南 二. 头等函数 2.1 一等公民 和 高阶函数 2.2 函数作为参数 ...

  8. 写 Python 代码不可不知的函数式编程技术

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 本文转自|深度学习这件小事 本文对 Python 中的函数式编程技 ...

  9. 函数式编程语言python-写 Python 代码不可不知的函数式编程技术

    近来,越来越多人使用函数式编程(functional programming).因此,很多传统的命令式语言(如 Java 和 Python)开始支持函数式编程技术.本文对 Python 中的函数式编程 ...

最新文章

  1. 心得丨学习人工智能AI需要哪些最基础的知识?
  2. 多线程服务器模型-one loop per thread
  3. 运维太忙?那是你还没掌握 Ansible !
  4. windows窗口移动到屏幕外,找回方法
  5. 【TensorFlow】Windows10 64位下安装TensorFlow - 官方原生GPU版安装
  6. 初级黑客安全技术命令详解
  7. Net作业调度(二) -CrystalQuartz远程管理
  8. Wordpress在主题中自定义登陆页面并且禁用自带的登陆页面
  9. acl在内核里的位置_在Linux中使用ACL(访问控制列表)保护文件/目录
  10. 将PowerPCB 中的pcb文件导入到Protel中使用
  11. 利用nat123进行内网穿透
  12. 阿里安全SQL注入检测 前五名队伍分享
  13. 软件测试岗位具体是做什么的?
  14. AlphaZero如何学习国际象棋的?
  15. 电子制作——一个电调的诞生之路
  16. 6.2.1 副本管理器
  17. 计算机软件可以授予专利权吗,软件产品能申请专利吗?
  18. 市场调研-全球与中国企业数据丢失预防(EDLP)产品市场现状及未来发展趋势
  19. 大麦网信息泄露致用户被骗 承认存在技术漏洞
  20. 图片水平垂直居中的几种方法总结

热门文章

  1. 征服上司站稳脚13招
  2. Kali与Shell编程 -- ZIP压缩包加密和解密
  3. PHP常见三种设计模式:单例、工厂、观察者
  4. Python语言学习讲解十:列表推导式
  5. 第一章 天行健,君子以自强不息 ——第一章 程序点滴之读书笔记之一
  6. java 关注公众号回调_处理公众号回调消息
  7. Keep It for Mac 1.3.4 强大的笔记本工具 破解版下载
  8. 数据分析实际案例之:pandas在餐厅评分数据中的使用
  9. 计算机启动u盘启动不了怎么办,电脑没有U盘启动项怎么办?主板不支持U盘启动怎么办?...
  10. 卖家注意!淘宝“七天无理由退货”规则将升级