900字范文,内容丰富有趣,生活中的好帮手!
900字范文 > Kotlin 函数式编程(Kotlin Functional Programming)

Kotlin 函数式编程(Kotlin Functional Programming)

时间:2021-05-23 19:18:42

相关推荐

Kotlin 函数式编程(Kotlin Functional Programming)

Kotlin函数式编程

(KotlinFunctionalProgramming)

陈光剑

1.函数式概述6

1.1.函数式简史6

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

1.2.1.概述7

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

1.3.函数式编程的特征10

1.3.1.函数是"第一等公民"(First-classandhigher-orderfunctions)10

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

1.3.3.不修改状态11

1.3.4.引用透明(Referentialtransparency)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.函数调用与Stack14

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.Applicative17

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.领域特定语言DSL25

8.1.DSL简介25

8.1.1.什么是DSL25

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

8.2.Kotlin生态中的DSL案例25

8.2.1.Gradle构建脚本25

8.2.2.SpringBean的新玩法25

8.2.3.简化Android开发的Anko25

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

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.函数式简史

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

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

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

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

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

函数式编程起源于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块(也就是高阶函数)被纳入Java8,Java语言也终于披挂上函数式的武器。JavaScript,这种也许算得上使用最为广泛的语言,本身就拥有不少函数式特性。就连最老成持重的C++语言,也在版的语言标准里增加了lambda块。

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

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

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

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

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

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

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

1.2.2.函数式编程语言介绍

1.2.2.1.Lisp

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

•CommonLISP特性

–它与机器无关

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

–它允许动态更新程序。

–它提供高级调试。

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

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

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

–它是基于表达的。

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

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

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

•Lisp实现Fibonacci数列函数:

(defunfib(n)

(if(<=n1)

1

(+(fib(-n1))

(fib(-n2)))))

(fib10)

89

1.2.2.2.Clojure

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

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

Clojureisafunctionalprogramminglanguage.Itprovidesthetoolstoavoidmutablestate,providesfunctionsasfirst-classobjects,andemphasizesrecursiveiterationinsteadofside-effectbasedlooping.Clojureisimpure,inthatitdoesn’tforceyourprogramtobereferentiallytransparent,anddoesn’tstrivefor'provable'programs.ThephilosophybehindClojureisthatmostpartsofmostprogramsshouldbefunctional,andthatprogramsthataremorefunctionalaremorerobust.

user=>(defhello(fn[]"HelloWorld"))

#'user/hello

user=>(hello)

"HelloWorld"

1.2.2.3.Haskell

Anadvanced,purelyfunctionalprogramminglanguage

Haskellislazy

Thatmeansthatunlessspecificallytoldotherwise,Haskellwon'texecutefunctionsandcalculatethingsuntilit'sreallyforcedtoshowyouaresult.

Haskellisstaticallytyped

Whenyoucompileyourprogram,thecompilerknowswhichpieceofcodeisanumber,whichisastringandsoon.Thatmeansthatalotofpossibleerrorsarecaughtatcompiletime.Ifyoutrytoaddtogetheranumberandastring,thecompilerwillwhineatyou.Haskellusesaverygoodtypesystemthathastypeinference.Thatmeansthatyoudon'thavetoexplicitlylabeleverypieceofcodewithatypebecausethetypesystemcanintelligentlyfigureoutalotaboutit.

Haskelliselegantandconcise

Becauseitusesalotofhighlevelconcepts,Haskellprogramsareusuallyshorterthantheirimperativeequivalents.Andshorterprogramsareeasiertomaintainthanlongeronesandhavelessbugs.

环境安装

Runthefollowinginyourterminal(asauserotherthanroot),thenfollowtheonscreeninstructions.

curlhttps://get--sSf|sh

教程

LearnYouaHaskellforGreatGood!:/chapters

1.2.2.4.Erlang

简介

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

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

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

教程

GettingStartedwithErlangUser'sGuide:/doc/gettingstarted/usersguide.html

1.2.2.5.JavaScript

1.2.2.6.Python

1.2.2.7.Java8

1.2.2.8.Scala

1.2.2.9.Kotlin

1.3.函数式编程的特征

1.3.1.函数是"第一等公民"(First-classandhigher-orderfunctions)

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

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

纯函数(Purefunctions,nosideeffects(memoryorI/O))。

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

函数式编程强调没有"副作用",callingthepurefunctionagainwiththesameargumentsreturnsthesameresult.

1.3.3.不修改状态

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

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

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

1.3.4.引用透明(Referentialtransparency)

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

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

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等现代编程语言中出现了越来越多的函数式特性(Java8),函数式编程的语法只是表象,而我们真正需要掌握的是函数式编程的新思维。

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

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

•函数式编程思维,脱离特定的语言特性,关注各种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的元素就两种,一种是函数(”purefunction“的概念,表现为命名句柄或表达式,所以独立性->并发性),一种是状态(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(大写Λ,小写λ)演算是一套用于研究函数定义、函数应用和递归的形式系统。它由AlonzoChurch和StephenColeKleene在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年被StephenKleene和J.B.Rosser提出的Kleene–Rosser悖论证明为是前后矛盾的,接着,在1936年,Church单独出版了λ演算模型中的和纯计算有关的部分,也就是如今被称为的无类型λ演算。在1940年,他提出了一个弱化计算,但是逻辑自洽的形式系统,如今被称之为简单类型λ演算。

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

PeterLandin1965年在论文ACorrespondencebetweenALGOL60andChurch'sLambda-notation中指出的一样,面向过程的程序设计语言在λ演算模型的体系中是可以理解的,因为它提供了基本的过程抽象和程序应用机制。

匿名函数

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

(lambda(x)(*xx))

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

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

3.4.λ演算归约策略

3.4.1.α规约

3.4.2.β-归约

3.4.3.Applicative

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

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

β-归约(β-reduction)

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

应用次序(Applicativeorder)

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

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

4.传名调用(Callbyname)和正常次序一样,但是不会在抽象的函数体中再进行归约,比如说,λ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.fx

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

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

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

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

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

加法是这样定义的:

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

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

PLUS23与5

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

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

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

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

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

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

或者

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

注意(g1)(λu.PLUS(gk)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.MULTn(FACT(PREDn)))1

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

FACT=Y(λg.λn.n(λu.MULTn(g(PREDn)))1)

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

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和TemplateHaskell为例论述在函数式语言中为了支持元编程需要扩展的机制,包括语法、语义、类型系统、安全的变量使用等,以及它们的实现方案、各方案的特点。最后总结一些元编程系统的共同点,并预测未来的发展趋势。

6.1.什么是元编程

6.1.1.简介

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

6.1.2.元编程与反射

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

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

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

另一个很常用的元编程例子是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.抽象到极致

什么是functionalprogramming?

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

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

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

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

2.immutable,也可叫purity或referentiallytransparent

referentiallytransparent可由以下两条规则概括:

Anexpressioneisreferentiallytransparentifforallprogramsp,everyoccurrenceofeinpcanbereplacedwiththeresultofevaluatingewithoutchangingtheresultofevaluatingp.

Afunctionfispureiftheexpressionf(x)isreferentiallytransparentforallreferentiallytransparentx.

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

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

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

其它的如GeneralizingFunctions,ADT,PatternMatching,LazyEvaluation,CPS...都可视为上面特性的扩展/附加,也是FP及typetheory不断发展的产物,为了更加类型安全,为了更好的FunctionalComposition(函数/计算的组合)

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

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

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.SpringBean的新玩法

8.2.3.简化Android开发的Anko

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

8.4.小结

9.函子与单子

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。