认知函数式编程

Posted 可视化实验室VSLAB

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了认知函数式编程相关的知识,希望对你有一定的参考价值。

一、前言:

近几年又有一个很古老的概念逐渐在程序员圈里被炒热起来——函数式编程,最近团队内的分享也涉及了一些相关概念。 本文不教技术、不炒概念,只从定义、历史、对比、语言、特性等几个方面给大家简单科普下函数式编程,旨在让大家对其有一个最基本而又相对全面的了解。
二、定义:

我们来看下函数式编程的定义。话不多说,先上维基百科:“函数式编程(functional programming)或称函数程序设计、泛函编程,是一种编程范式,它将电脑运算视为函数运算,并且避免使用程序状态以及易变对象。其中,λ演算(lambda calculus)为该语言最重要的基础。而且,λ演算的函数可以接受函数当作输入和输出。
比起指令式编程,函数式编程更加强调程序执行的结果而非执行的过程,倡导利用若干简单的执行单元让计算结果不断渐进,逐层推导复杂的运算,而不是设计一个复杂的执行过程。”
据此,我们看出函数式编程的最主要的特征——函数是一等公民(first-class),以及基本理念——关注函数的抽象(或结果)而非过程。
三、简史:

说到一个概念不得不阐述下相关历史。函数式编程的理论基础是λ演算,其本身是一种数学的抽象但不是编程语言。
1930 年代,在普林斯顿获得本科学位后被邀留在研究生院继续攻读,致力于解决抽象的数学难题的阿隆佐·丘奇,通过和其他人的合作,提出了一个被称为 λ 演算的形式系统。
这个系统本质上是一种虚拟的机器的编程语言,他的基础是一些以函数为参数和返回值的函数。函数用希腊字母 λ 标识,这个形式系统因此得名。利用这一形式系统,阿隆佐就可以对上述诸多问题推理并给出结论性的答案。
独立于阿隆佐,阿兰·图灵也在进行着相似的工作,他提出了一个不同的形式系统(现在被称为图灵机),并使用这一系统独立地给出了和阿隆佐相似的结论。后来人们证明图灵机和 λ 演算能力等同。
四、对比

1. 图灵机和λ演算
上面也说到了后来人们证明图灵机和 λ 演算能力等同,那么两者的区别又是什么呢?
(1)图灵机:
1936年,英国数学家阿兰.麦席森.图灵为了解决希尔伯特著名的第十问题而提出有效计算模型——图灵机。进而作出了可计算理论和现代计算机的奠基性工作,著名的停机问题给出了机械计算模型的能力极限,其深刻的意义和漂亮的证明使它成为可计算理论中的标志性定理之一。
图灵机,又称图灵计算机,即将人们使用纸笔进行数学运算的过程进行抽象,由一个虚拟的机器替代人类进行数学运算。
Ps:这里说的图灵机仅对标我们现在所说的程序,而不是我们的计算机。
(2)λ演算:
阿隆佐.邱奇,跟图灵同时代的天才,则从另一个抽象角度提出了λ演算的思想,与图灵机抽象的倾向于硬件性不同,邱奇的λ演算理论是从数学的角度进行抽象,不关心运算的机械过程而只关心运算的抽象性质,只用最简洁的几条公理便建立起了与图灵机完全等价的计算模型,其体现出来的数学抽象美开出了函数式编程语言这朵奇葩。
LispHaskell… 这些以抽象性和简洁美为特点的语言至今仍然活跃在计算机科学界,虽然由于其本质上源于λ演算理论的抽象方式不符合人的思维习惯从而注定无法成为主流的编程语言,然而这仍然无法妨碍它们成为编程理论乃至计算机学科的最佳教本。
2. 面向对象编程和函数式编程
如果面向对象式编程是用名词抽象世界,从而达到对于事物的封装和重用。
那函数式编程对应的就是用动词抽象世界,从而达到对于行为的封装和重用。
3. 函数式编程和命令式编程
函数式编程关心数据的映射,用计算来表示程序, 用计算的组合来表达程序的组合。
命令式编程关心解决问题的步骤,用命令来表示程序, 用命令的顺序执行来表达程序的组合。
函数式编程中的函数是指数学中的函数,即自变量的映射。也就是说一个函数的值仅决定于函数参数的值,不依赖其他状态。只要参数值不变,不论什么时候调用,调用几次,值都是不变的。
在函数式语言中,函数作为一等公民,可以在任何地方定义,在函数内或函数外,可以作为函数的参数和返回值,可以对函数进行组合。
纯函数式编程语言中的变量是代数中的变量,即一个值的名称。变量的值是不可变,也就是说不允许像命令式编程语言中那样多次给一个变量赋值。
函数式语言的如条件语句,循环语句也不是命令式编程语言中的控制语句,而是函数的语法糖,比如在 Scala语言中,if else不是语句而是三元运算符,是有返回值的。
从理论上说,函数式语言也不是通过冯诺伊曼体系结构的机器上运行的,而是通过λ演算来运行的,就是通过变量替换的方式进行,变量替换为其值或表达式,函数也替换为其表达式,并根据运算符进行计算。
五、语言

 λ 演算并非设计来于计算机上执行,但是函数式编程思想本身也用于很多编程语言的设计之中。下面简单介绍几个较为典型的函数式编程思想的语言。
1. Lisp(家族)
Lisp语言作为第二古老的高级编程语言(第一是 FORTRAN),以及第一个基于 λ 演算来设计的函数式语言(非完全函数式)让我们不得不提及一下。
现代编程语言中最基本的也是标配的 if-then-else 判断结构最早就是 McCarthy 为了写 Lisp 而创造出来的。树形结构、垃圾回收机制、动态类型也是最早在 Lisp 中体现。
说到函数式编程,Lisp 则算是最早的实践者。这个直接影响了所有具有函数式编程特性的编程语言如 RubyPythonjavascriptHaskellScalaLua等等。
Lisp的灵活和强大导致其方言很多, Common LispSchemaClojurenewLISP等。仅Common Lisp一种方言都有十几种开源商业实现可选。
2. Haskell
Haskell是一种标准化的,通用的纯函数编程语言,有非限定性语义和强静态类型。是现代函数编程语言的典型代表,也是强类型语言和支持类型推导的典型代表。
Haskell语言是1990年在编程语言Miranda的基础上标准化的,并且以λ演算为基础发展而来。具有“证明即程序、结论公式即程序类型”的特征。
3. Javascript
JS是前端开发同学最熟悉的一种语言,在业内被戏称为“披着C外衣的Lisp”。
Javascript的设计者Brendan Eich花了10天的时间设计了它。Eich的主要兴趣是函数式编程,在他设计的新语言中加入了函数编程语言的特性。Eich设计的主要思想为:
(1)借鉴 C语言的基本语法;
(2)借鉴 Java语言的数据类型和内存管理;
(3)借鉴 Scheme语言,将函数提升到"第一等公民";
(4)借鉴 Self语言,使用基于原型的继承机制。
JS的函数式编程思想就是来自于第三点,first-class function。所以我们在写JS的时候经常会涉及一些高阶函数和闭包等函数式语言的特征。
六、特性

1. 惰性计算
在惰性计算中,表达式不是在绑定到变量时立即计算,而是在求值程序需要产生表达式的值时进行计算。
延迟的计算使您可以编写可能潜在地生成无穷输出的函数。因为不会计算多于程序的其余部分所需要的值,所以不需要担心由无穷计算所导致的 out-of-memory 错误。一个惰性计算的例子是生成无穷 Fibonacci 列表的函数,但是对第n个Fibonacci 数的计算相当于只是从可能的无穷列表中提取一项。
2. 递归
函数式还有一个特点是用递归作为控制流程的机制。例如,Lisp 处理的列表定义为在头元素后面有子列表,这种表示法使得它自己自然地对更小的子列表不断递归。
3. 函数是"第一等公民"
所谓"第一等公民"(first class),指的是函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值。
4. 只用"表达式",不用"语句"
"表达式"(expression)是一个单纯的运算过程,总是有返回值;"语句"(statement)是执行某种操作,没有返回值。函数式编程要求,只使用表达式,不使用语句。也就是说,每一步都是单纯的运算,而且都有返回值。
原因是函数式编程的开发动机,一开始就是为了处理运算,不考虑外部I/O。"语句"属于对系统的读写操作,所以就被排斥在外。
当然,实际应用中,不做I/O是不可能的。因此,编程过程中,函数式编程只要求把I/O限制到最小,不要有不必要的读写行为,保持计算过程的单纯性。
5. 没有"副作用"
所谓"副作用"指的是函数内部与外部互动(最典型的情况,就是修改全局变量的值),产生运算以外的其他结果。
函数式编程强调没有"副作用",意味着函数要保持独立,所有功能就是返回一个新的值,没有其他行为,尤其是不得修改外部变量的值。
七、优点

1. 代码简洁,开发快速
函数式编程大量使用函数,减少了代码的重复,因此程序比较短,开发速度较快。
Paul Graham在《黑客与画家》一书中写道:同样功能的程序,极端情况下,Lisp代码的长度可能是C代码的二十分之一。
2. 接近自然语言,易于理解
函数式编程的自由度很高,可以写出很接近自然语言的代码。
将表达式(1 + 2) * 3 - 4,写成函数式语言:
subtract(multiply(add(1,2), 3), 4)
对它进行变形,不难得到另一种写法:
add(1,2).multiply(3).subtract(4)
这基本就是自然语言的表达了。再看下面的代码,大家应该一眼就能明白它的意思吧:
merge([1,2],[3,4]).sort().search("2")
因此,函数式编程的代码更容易理解。
3. 更方便的代码管理
函数式编程不依赖、也不会改变外界的状态,只要给定输入参数,返回的结果必定相同。因此,每一个函数都可以被看做独立单元,很有利于进行单元测试和除错,以及模块化组合。
4. 易于"并发编程"
函数式编程不需要考虑"死锁",因为它不修改变量,所以根本不存在"锁"线程的问题。不必担心一个线程的数据,被另一个线程修改,所以可以很放心地把工作分摊到多个线程,部署"并发编程"。
请看下面的代码:
var s1 = Op1();var s2 = Op2();var s3 = concat(s1, s2);
由于s1和s2互不干扰,不会修改变量,谁先执行是无所谓的,所以可以放心地增加线程,把它们分配在两个线程上完成。 其他类型的语言就做不到这一点,因为s1可能会修改系统状态,而s2可能会用到这些状态,所以必须保证s2在s1之后运行,自然也就不能部署到其他线程上了。
多核CPU是将来的潮流,所以函数式编程的这个特性非常重要。
5. 代码的热升级
函数式编程没有副作用,只要保证接口不变,内部实现是与外部无关的。所以,可以在运行状态下直接升级代码,不需要重启,也不需要停机。 Erlang语言早就证明了这一点,它是瑞典爱立信公司为了管理电话系统而开发的,电话系统的升级当然是不能停机的。


以上是关于认知函数式编程的主要内容,如果未能解决你的问题,请参考以下文章

函数式编程/命令式编程

《On Java 8》中文版 第十三章 函数式编程

Rx系列---响应式编程

如何理解函数式编程?

web代码片段

html 将以编程方式附加外部脚本文件的javascript代码片段,并按顺序排列。用于响应式网站,其中ma