纯粹的函数式编程是什么意思指的是什么意思

帐号:密码:下次自动登录{url:/nForum/slist.json?uid=guest&root=list-section}{url:/nForum/nlist.json?uid=guest&root=list-section}
贴数:16&分页:suac发信人: friedcat (suac), 信区: FuncProgram
标&&题: Re: 维基百科说Scheme是一种近似函数式编程语言,它不纯粹吗?
发信站: 水木社区 (Sat Jan 22 23:33:49 2011), 站内 && undefined = error = _|_
它们都不对应任何数学元素,因为它们可以是空集的元素,而这在数学上不可能 && type EmptySet = forall a. a && x :: EmptySet
x = undefined && x :: EmptySet
x = error "empty set"
【 在 lars (蓝天白云) 的大作中提到: 】
: 这个case的例子是不是evaluate出了 undefined? _|_ ?
: 无限递归那个是lfp等于undefined吧,似乎编译器可以算出其lfp == _|_?
  有一种理论宣称,如果任何一个人真正发现了宇宙存在的原因、宇宙存在的目的,宇宙就会立刻消失,被某种更为怪异、更难以理解的玩意儿取代。
  还有另外一种理论宣称,上述事件已经发生了。 && ※ 来源:·水木社区 ·[FROM: 71.235.81.*]
蓝天白云发信人: lars (蓝天白云), 信区: FuncProgram
标&&题: Re: 维基百科说Scheme是一种近似函数式编程语言,它不纯粹吗?
发信站: 水木社区 (Sat Jan 22 23:35:38 2011), 站内 && 多谢大牛科普,我以为唯一不pure functional的地方就是unsafePerformIO呢 && 【 在 friedcat (suac) 的大作中提到: 】
: undefined = error = _|_
: 它们都不对应任何数学元素,因为它们可以是空集的元素,而这在数学上不可能
: type EmptySet = forall a. a
: ...................
┈╆─╮┈┈╋───╋┈┊┈╬──┼┈┈┈┈┼┼┈┈╬──╋┈┈┈
&&&&│找│&&&&│找不到┼─█┈┤我的├──╬┼┼┼╭─┼╮美│&&&&&&
&&┈█─┼┈┈█───┼你│&&╚──┤世界│┼█─╢不│┗─█─╮&&
&&&&│你│&&&&│&&&&&&╬─╯&&&&&&┈╩──╯&&│&&│再│&&&&│丽│&&
&&┈┗─┼┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈│┈┼─╬┈┈╬─╯┈
&&&& ※ 来源:·水木社区 newsmth.net·[FROM: 143.89.88.*]
CSer发信人: royhu (CSer), 信区: FuncProgram
标&&题: Re: 维基百科说Scheme是一种近似函数式编程语言,它不纯粹吗?
发信站: 水木社区 (Sun Jan 23 05:13:20 2011), 站内 && 不对吧,你把pure和total混为一谈了。
【 在 friedcat (suac) 的大作中提到: 】
: 是坑爹的函数式
: 别说IO monad了,haskell的error,case ... of ...和递归全都不是纯函数式的
: 要用真正纯粹的函数式语言,请看这里:
: ...................
When the only 'tool' you have is a coq, every problem looks like a qunt. &&&& ※ 来源:·水木社区 newsmth.net·[FROM: 199.111.222.*]
suac发信人: friedcat (suac), 信区: FuncProgram
标&&题: Re: 维基百科说Scheme是一种近似函数式编程语言,它不纯粹吗?
发信站: 水木社区 (Sun Jan 23 10:55:06 2011), 站内 && 不total就没法真正的pure
死循环和报错都是副作用
【 在 royhu (CSer) 的大作中提到: 】
: 不对吧,你把pure和total混为一谈了。
  有一种理论宣称,如果任何一个人真正发现了宇宙存在的原因、宇宙存在的目的,宇宙就会立刻消失,被某种更为怪异、更难以理解的玩意儿取代。
  还有另外一种理论宣称,上述事件已经发生了。 && ※ 来源:·水木社区 ·[FROM: 71.235.81.*]
CSer发信人: royhu (CSer), 信区: FuncProgram
标&&题: Re: 维基百科说Scheme是一种近似函数式编程语言,它不纯粹吗?
发信站: 水木社区 (Sun Jan 23 11:09:04 2011), 站内 && 我不这样认为。Pure就是(在有定义的地方)相同的参数返回相同的结果,数学中的函数也并不是都是total的。在theorem prover中需要totalness是因为不这样的话就不sounde了,可以证明出假命题。Haskell中的一个函数如果传递一个相同的参数总是死循环或者报错,为什么不是pure的呢?
【 在 friedcat (suac) 的大作中提到: 】
: 不total就没法真正的pure
: 死循环和报错都是副作用
When the only 'tool' you have is a coq, every problem looks like a qunt. &&&& ※ 来源:·水木社区 newsmth.net·[FROM: 199.111.222.*]
suac发信人: friedcat (suac), 信区: FuncProgram
标&&题: Re: 维基百科说Scheme是一种近似函数式编程语言,它不纯粹吗?
发信站: 水木社区 (Sun Jan 23 11:19:19 2011), 站内 && 我倾向于认为定义域应该表现在类型里,既然一个函数类型是Int,那么就应该在整个Int里都有定义。反之该语言不能称为pure functional。 && 这应该才是我想表达的,我之前的说法引起误导了,不好意思:P && 【 在 royhu (CSer) 的大作中提到: 】
: 我不这样认为。Pure就是(在有定义的地方)相同的参数返回相同的结果,数学中的函数也并不是都是total的。在theorem prover中需要totalness是因为不这样的话就不sounde了,可以证明出假命题。Haskell中的一个函数如果传递一个相同的参数总是死循环或者报错,为什么不是pure的呢?
  有一种理论宣称,如果任何一个人真正发现了宇宙存在的原因、宇宙存在的目的,宇宙就会立刻消失,被某种更为怪异、更难以理解的玩意儿取代。
  还有另外一种理论宣称,上述事件已经发生了。 && ※ 来源:·水木社区 ·[FROM: 71.235.81.*]
文章数:16&分页:函数式编程是什么东东?
这一节是说,python更适合用来进行科学计算等纯数学研究嘛?
写下你的评论...
Copyright (C)
All Rights Reserved | 京ICP备 号-2& 什么是函数式编程——函数式编程扫盲贴
什么是函数式编程——函数式编程扫盲贴
& & & &1. 概论& & & &在过去的近十年的时间里,面向对象编程大行其道。以至于在大学的教育里,老师也只会教给我们两种编程模型,面向过程和面向对象。& & & &孰不知,在面向对象产生之前,在面向对象思想产生之前,函数式编程已经有了数十年的历史。& & & &那么,接下来,就让我们回顾这个古老又现代的编程模型,让我们看看究竟是什么魔力将这个概念,将这个古老的概念,在21世纪的今天再次拉入了我们的视野。& & & &2. 什么是函数式编程& & & &在维基百科中,已经对函数式编程有了很详细的介绍。& & & &那我们就来摘取一下Wiki上对Functional Programming的定义:In computer science, functional programming is a programming paradigm that treats computation as the evaluation of mathematical functions and avoids state and mutable data.& & & &简单地翻译一下,也就是说函数式编程是一种编程模型,他将计算机运算看做是数学中函数的计算,并且避免了状态以及变量的概念。& & & &接下来,我们就来剖析下函数式编程的一些特征。& & & &3. 从并发说开来& & & &说来惭愧,我第一个真正接触到函数式编程,要追溯到两年以前的《Erlang程序设计》,我们知道Erlang是一个支持高并发,有着强大容错性的函数式编程语言。& & & &因为时间太久了,而且一直没有过真正地应用,所以对Erlang也只是停留在一些感性认识上。在我眼里,Erlang对高并发的支持体现在两方面,第一,Erlang对轻量级进程的支持(请注意此处进程并不等于操作系统的进程,而只是Erlang内部的一个单位单元),第二,就是变量的不变性。& & & &4. 变量的不变性& & & &在《Erlang程序设计》一书中,对变量的不变性是这样说的,Erlang是目前唯一变量不变性的语言。具体的话我记不清了,我不知道是老爷子就是这么写的,还是译者的问题。我在给这本书写书评的时候吹毛求疵地说:& & & &我对这句话有异议,切不说曾经的Lisp,再到如今的F#都对赋值操作另眼相看,低人一等。单说如今的Java和C#,提供的final和readonly一样可以支持变量的不变性,而这个唯一未免显得有点太孤傲了些。& & & &让我们先来看两段程序,首先是我们常见的一种包含赋值的程序:
class&Account:&&&&&
&&&&def&__init__(self,balance):&&&&&
&&&&&&&&self.balance&=&balance&&&&&
&&&&def&desposit(self,amount):&&&&&
&&&&&&&&self.balance&=&self.balance&+&amount&&&&&
&&&&&&&&return&self.balance&&&&&
&&&&def&despositTwice(self):&&&&&
&&&&&&&&self.balance&=&self.balance&*&2&&&&&
&&&&&&&&return&self.balance&&&&
if&__name__&==&'__main__':&&&&&
&&&&account&=&Account(100)&&&&&
&&&&print(account.desposit(10))&&&&&
&&&&print(account.despositTwice()) & && & & &这段程序本身是没有问题的,但是我们考虑这样一种情况,现在有多个进程在同时跑这一个程序,那么程序就会被先desposit 还是先 despositTwice所影响。& & & &但是如果我们采用这样的方式:
def&makeAccount(balance):&&&&&
&&&&global&desposit&&&&&
&&&&global&despositTwice&&&&&
&&&&def&desposit(amount):&&&&&
&&&&&&&&result&=&balance&+&amount&&&&&
&&&&&&&&return&result&&&&&
&&&&def&despositTwice():&&&&&
&&&&&&&&result&=&balance&*&2&&&&&
&&&&&&&&return&result&&&&&
&&&&def&dispatch(method):&&&&&
&&&&&&&&return&eval(method)&&&&&
&&&&return&dispatch&&&&
if&__name__&==&'__main__':&&&&&
&&&&handler&=&makeAccount(100)&&&&&
&&&&print(handler('desposit')(10))&&&&&
&&&&print(handler('despositTwice')()) & && & & &这时我们就会发现,无论多少个进程在跑,因为我们本身没有赋值操作,所以都不会影响到我们的最终结果。& & & &但是这样也像大家看到的一样,采用这样的方式没有办法保持状态。& & & &这也就是我们在之前概念中看到的无状态性。& & & &5. 再看函数式编程的崛起& & & &既然已经看完了函数式编程的基本特征,那就让我们来想想数十年后函数式编程再次崛起的幕后原因。& & & &一直以来,作为函数式编程代表的Lisp,还是Haskell,更多地都是在大学中,在实验室中应用,而很少真的应用到真实的生产环境。& & & &先让我们再来回顾一下伟大的摩尔定律:& & & &1、集成电路芯片上所集成的电路的数目,每隔18个月就翻一番。& & & &2、微处理器的性能每隔18个月提高一倍,而价格下降一半。& & & &3、用一个美元所能买到的电脑性能,每隔18个月翻两番。& & & &一如摩尔的预测,整个信息产业就这样飞速地向前发展着,但是在近年,我们却可以发现摩尔定律逐渐地失效了,芯片上元件的尺寸是不可能无限地缩小的,这就意味着芯片上所能集成的电子元件的数量一定会在某个时刻达到一个极限。那么当技术达到这个极限时,我们又该如何适应日益增长的计算需求,电子元件厂商给出了答案,就是多核。& & & &多核并行程序设计就这样被推到了前线,而命令式编程天生的缺陷却使并行编程模型变得非常复杂,无论是信号量,还是锁的概念,都使程序员不堪其重。& & & &就这样,函数式编程终于在数十年后,终于走出实验室,来到了真实的生产环境中,无论是冷门的Haskell,Erlang,还是Scala,F#,都是函数式编程成功的典型。& & & &6. 函数式编程的第一型& & & &我们知道,对象是面向对象的第一型,那么函数式编程也是一样,函数是函数式编程的第一型。& & & &我们在函数式编程中努力用函数来表达所有的概念,完成所有的操作。& & & &在面向对象编程中,我们把对象传来传去,那在函数式编程中,我们要做的是把函数传来传去,而这个,说成术语,我们把他叫做高阶函数。& & & &那我们就来看一个高阶函数的应用,熟悉的同学应该对下面的代码很熟悉,让哦我们来写一个在电子电路中常用的滤波器的示例代码。
def&Filt(arr,func):&&&&&
&&&&result&=&[]&&&&&
&&&&for&item&in&arr:&&&&&
&&&&&&&&result.append(func(item))&&&&&
&&&&return&result&&&&
def&MyFilter(ele):&&&&&
&&&&if&ele&&&0&:&&&&&
&&&&&&&&return&0&&&&&
&&&&return&ele&&&&
if&__name__&==&'__main__':&&&&&
&&&&arr&=&[-5,3,5,11,-45,32]&&&&&
&&&&print('%s'&%&(Filt(arr,MyFilter))) & && & & &哦,之前忘记了说,什么叫做高阶函数,我们给出定义:& & & &在数学和计算机科学中,高阶函数是至少满足下列一个条件的函数:& & & &&&接受一个或多个函数作为输入& & & &&&输出一个函数& & & &那么,毫无疑问上面的滤波器,就是高阶函数的一种应用。& & & &在函数式编程中,函数是基本单位,是第一型,他几乎被用作一切,包括最简单的计算,甚至连变量都被计算所取代。在函数式编程中,变量只是一个名称,而不是一个存储单元,这是函数式编程与传统的命令式编程最典型的不同之处。& & & &让我们看看,变量只是一个名称,在上面的代码中,我们可以这样重写主函数:
if&__name__&==&'__main__':&&&&&
&&&&arr&=&[-5,3,5,11,-45,32]&&&&&
&&&&func&=&MyFilter&&&&&
&&&&print('%s'&%&(Filt(arr,func))) & && & & &当然,我们还可以把程序更精简一些,利用函数式编程中的利器,map,filter和reduce :
if&__name__&==&'__main__':&&&&&
&&&&arr&=&[-5,3,5,11,-45,32]&&&&&
&&&&print('%s'&%&(map(lambda&x&:&0&if&x&0&else&x&,arr))) & && & & &这样看上去是不是更赏心悦目呢?& & & &这样我们就看到了,函数是我们编程的基本单位。& & & &7. 函数式编程的数学本质& & & &忘了是谁说过:一切问题,归根结底到最后都是数学问题。& & & &编程从来都不是难事儿,无非是细心,加上一些函数类库的熟悉程度,加上经验的堆积,而真正困难的,是如何把一个实际问题,转换成一个数学模型。这也是为什么微软,之类的公司重视算法,这也是为什么数学建模大赛在大学计算机系如此被看重的原因。& & & &先假设我们已经凭借我们良好的数学思维和逻辑思维建立好了数学模型,那么接下来要做的是如何把数学语言来表达成计算机能看懂的程序语言。& & & &这里我们再看在第四节中,我们提到的赋值模型,同一个函数,同一个参数,却会在不同的场景下计算出不同的结果,这是在数学函数中完全不可能出现的情况,f(x) = y ,那么这个函数无论在什么场景下,都会得到同样的结果,这个我们称之为函数的确定性。& & & &这也是赋值模型与数学模型的不兼容之处。而函数式编程取消了赋值模型,则使数学模型与编程模型完美地达成了统一。& & & &8. 函数式编程的抽象本质& & & &相信每个程序员都对抽象这个概念不陌生。& & & &在面向对象编程中,我们说,类是现实事物的一种抽象表示。那么抽象的最大作用在我看来就在于抽象事物的重用性,一个事物越具体,那么他的可重用性就越低,因此,我们再打造可重用性代码,类,类库时,其实在做的本质工作就在于提高代码的抽象性。而再往大了说开来,程序员做的工作,就是把一系列过程抽象开来,反映成一个通用过程,然后用代码表示出来。& & & &在面向对象中,我们把事物抽象。而在函数式编程中,我们则是在将函数方法抽象,第六节的滤波器已经让我们知道,函数一样是可重用,可置换的抽象单位。& & & &那么我们说函数式编程的抽象本质则是将函数也作为一个抽象单位,而反映成代码形式,则是高阶函数。& & & &9.状态到底怎么办& & & &我们说了一大堆函数式编程的特点,但是我们忽略了,这些都是在理想的层面,我们回头想想第四节的变量不变性,确实,我们说,函数式编程是无状态的,可是在我们现实情况中,状态不可能一直保持不变,而状态必然需要改变,传递,那么我们在函数式编程中的则是将其保存在函数的参数中,作为函数的附属品来传递。& & & &ps:在Erlang中,进程之间的交互传递变量是靠&信箱&的收发信件来实现,其实我们想一想,从本质而言,也是将变量作为一个附属品来传递么!& & & 我们来看个例子,我们在这里举一个求x的n次方的例子,我们用传统的命令式编程来写一下:
def&expr(x,n):&&&&&
&&&&result&=&1&&&&&
&&&&for&i&in&range(1,n+1):&&&&&
&&&&&&&&result&=&result&*&x&&&&&
&&&&return&result&&&&
if&__name__&==&'__main__':&&&&&
&&&&print(expr(2,5)) &&& & & &这里,我们一直在对result变量赋值,但是我们知道,在函数式编程中的变量是具有不变性的,那么我们为了保持result的状态,就需要将result作为函数参数来传递以保持状态:
def&expr(num,n):&&&&&
&&&&if&n==0:&&&&&
&&&&&&&&return&1&&&&&
&&&&return&num*expr(num,n-1)&&&&
if&__name__&==&'__main__':&&&&&
&&&&print(expr(2,5)) & && & & &呦,这不是递归么!& & & &10. 函数式编程和递归& & & &递归是函数式编程的一个重要的概念,循环可以没有,但是递归对于函数式编程却是不可或缺的。& & & &在这里,我得承认,我确实不知道我该怎么解释递归为什么对函数式编程那么重要。我能想到的只是递归充分地发挥了函数的威力,也解决了函数式编程无状态的问题。(如果大家有其他的意见,请赐教)& & & &递归其实就是将大问题无限地分解,直到问题足够小。& & & &而递归与循环在编程模型和思维模型上最大的区别则在于:& & & &循环是在描述我们该如何地去解决问题。& & & &递归是在描述这个问题的定义。& & & &那么就让我们以斐波那契数列为例来看下这两种编程模型。& & & &先说我们最常见的递归模型,这里,我不采用动态规划来做临时状态的缓存,只是说这种思路:
def&Fib(a):&&&&&
&&&&if&a==0&or&a==1:&&&&&
&&&&&&&&return&1&&&&&
&&&&else:&&&&&
&&&&&&&&return&Fib(a-2)+Fib(a-1) & && & & &递归是在描述什么是斐波那契数列,这个数列的定义就是一个数等于他的前两项的和,并且已知Fib(0)和Fib(1)等于1。而程序则是用计算机语言来把这个定义重新描述了一次。& & & &那接下来,我们看下循环模型:
def&Fib(n):&&&&&
&&&&a=1&&&&&
&&&&b=1&&&&&
&&&&n&=&n&-&1&&&&&
&&&&while&n&0:&&&&&
&&&&&&&&temp=a&&&&&
&&&&&&&&a=a+b&&&&&
&&&&&&&&b=temp&&&&&
&&&&&&&&n&=&n-1&&&&&
&&&&return&b & && & & &这里则是在描述我们该如何求解斐波那契数列,应该先怎么样再怎么样。& & & &而我们明显可以看到,递归相比于循环,具有着更加良好的可读性。& & & &但是,我们也不能忽略,递归而产生的StackOverflow,而赋值模型呢?我们懂的,函数式编程不能赋值,那么怎么办?& & & &11. &尾递归,伪递归& & & &我们之前说到了递归和循环各自的问题,那怎么来解决这个问题,函数式编程为我们抛出了答案,尾递归。& & & &什么是尾递归,用最通俗的话说:就是在最后一部单纯地去调用递归函数,这里我们要注意&单纯&这个字眼。& & & &那么我们说下尾递归的原理,其实尾递归就是不要保持当前递归函数的状态,而把需要保持的东西全部用参数给传到下一个函数里,这样就可以自动清空本次调用的栈空间。这样的话,占用的栈空间就是常数阶的了。& & & &在看尾递归代码之前,我们还是先来明确一下递归的分类,我们将递归分成&树形递归&和&尾递归&,什么是树形递归,就是把计算过程逐一展开,最后形成的是一棵树状的结构,比如之前的斐波那契数列的递归解法。& & & &那么我们来看下斐波那契尾递归的写法:
def&Fib(a,b,n):&&&&&
&&&&if&n==0:&&&&&
&&&&&&&&return&b&&&&&
&&&&else:&&&&&
&&&&&&&&return&Fib(b,a+b,n-1) & && & & &这里看上去有些难以理解,我们来解释一下:传入的a和b分别是前两个数,那么每次我都推进一位,那么b就变成了第一个数,而a+b就变成的第二个数。& & & &这就是尾递归。其实我们想一想,这不是在描述问题,而是在寻找一种问题的解决方案,和上面的循环有什么区别呢?我们来做一个从尾递归到循环的转换把!& & & &最后返回b是把,那我就先声明了,b=0& & & &要传入a是把,我也声明了,a=1& & & &要计算到n==0是把,还是循环while n!=0& & & &每一次都要做一个那样的计算是吧,我用临时变量交换一下。temp= b=a+b;a=temp。& & & &那么按照这个思路一步步转换下去,是不是就是我们在上面写的那段循环代码呢?& & & &那么这个尾递归,其实本质上就是个&伪递归&,您说呢?& & & &既然我们可以优化,对于大多数的函数式编程语言的编译器来说,他们对尾递归同样提供了优化,使尾递归可以优化成循环迭代的形式,使其不会造成堆栈溢出的情况。& & & &12. 惰性求值与并行& & & &第一次接触到惰性求值这个概念应该是在Haskell语言中,看一个最简单的惰性求值,我觉得也是最经典的例子:& & & &在Haskell里,有个repeat关键字,他的作用是返回一个无限长的List,那么我们来看下:& & & &take 10 (repeat 1) && & & &就是这句代码,如果没有了惰性求值,我想这个进程一定会死在那里,可是结果却是很正常,返回了长度为10的List,List里的值都是1。这就是惰性求值的典型案例。& & & &我们看这样一段简单的代码:
def&getResult():&&&&&
&&&&a&=&getA()&&&//Take&a&long&time&&&&&
&&&&b&=&getB()&&&//Take&a&long&time&&&&&
&&&&c&=&a&+&b & && & & &这段代码本身很简单,在命令式程序设计中,编译器(或解释器)会做的就是逐一解释代码,按顺序求出a和b的值,然后再求出c。& & & &可是我们从并行的角度考虑,求a的值是不是可以和求b的值并行呢?也就是说,直到执行到a+b的时候我们编译器才意识到a和b直到现在才需要,那么我们双核处理器就自然去发挥去最大的功效去计算了呢!& & & &这才是惰性求值的最大威力。& & & &当然,惰性求值有着这样的优点也必然有着缺点,我记得我看过一个例子是最经典的:
def&Test():&&&&&
&&&&print('Please&enter&a&number:')&&&&&
&&&&a&=&raw_input() & && & & &可是这段代码如果惰性求值的话,第一句话就不见得会在第二句话之前执行了。& & & &13. 函数式编程总览& & & &我们看完了函数式编程的特点,我们想想函数式编程的应用场合。& & & &1. 数学推理& & & &2. 并行程序& & & &那么我们总体地说,其实函数式编程最适合地还是解决局部性的数学小问题,要让函数式编程来做CRUD,来做我们传统的逻辑性很强的Web编程,就有些免为其难了。& & & &就像如果要用Scala完全取代今天的Java的工作,我想恐怕效果会很糟糕。而让Scala来负责底层服务的编写,恐怕再合适不过了。& & & &而在一种语言中融入多种语言范式,最典型的C#。在C# 3.0中引入Lambda表达式,在C# 4.0中引入声明式编程,我们某些人在嘲笑C#越来越臃肿的同时,却忽略了,这样的语法糖,带给我们的不仅仅是代码书写上的遍历,更重要的是编程思维的一种进步。& & & &好吧,那就让我们忘记那些C#中Lambda背后的实现机制,在C#中,还是在那些更纯粹地支持函数式编程的语言中,尽情地去体验函数式编程带给我们的快乐把!
除非特别注明,文章均为原创
转载请标明本文地址:
作者:鸡啄米
&&( 10:43:10)&&( 9:59:44)&&( 10:51:3)&&( 10:9:13)&&( 9:50:41)&&( 11:14:16)&&( 14:11:21)&&( 11:43:45)&&( 8:45:5)&&( 13:32:30)
这是python代码吧,怎么写成C++的?鸡啄米 于
12:01:32 回复疏忽,谢谢提醒,已修改
完全随机文章使用 JavaScript 进行函数式编程 (一) 翻译
作者:simei231, 祝青, 李中凯
字体:[ ] 类型:转载 时间:
本文是函数式编程系列的第一篇文章。这里我会简要介绍一下编程范式,然后会直接介绍使用 Javascript 进行函数式编程的概念,因为 JavsScript 是最被认可的函数式程序语言之一。我们鼓励读者通过参考资料部分进一步了解这一迷人的概念
编程范式是一个由思考问题以及实现问题愿景的工具组成的框架。很多现代语言都是聚范式(或者说多重范式): 他们支持很多不同的编程范式,比如面向对象,元程序设计,泛函,面向过程,等等。
函数式编程范式
函数式编程就像一辆氢燃料驱动的汽车——先进的未来派,但是还没有被广泛推广。与命令式编程相反,他由一系列语句组成,这些语句用于更新执行时的全局状态。函数式编程将计算转化作表达式求值。这些表达式全由纯数学函数组成,这些数学函数都是一流的(可以被当做一般值来运用和处理),并且没有副作用。
函数式编程很重视以下值:
函数是一等要务
我们应该将函数与编程语言中的其他类对象同样对待。换句话说,您可以将函数存储在变量里,动态创建函数,以及将函数返回或者将函数传递给其他函数。下面我们来看一个例子...
一个字符串可以保存为一个变量,函数也可以,例如:
var&sayHello&=&function()&{&return&“Hello”&};
一个字符串可以保存为对象字段,函数也可以,例如:
var&person&=&{message:&“Hello”,&sayHello:&function()&{&return&“Hello”&}};
一个字符串可以再用到时才创建,函数也可以,例如:
“Hello&”&+&(function()&{&return&“World”&})();&//=&&Hello&World
一个字符串可以作为输入参数传给函数,则函数也可以:
&&&&function&hellloWorld(hello,&world)&{&return&hello&+&world()&}
一个字符串可以作为函数返回值,函数也可以,例如:
return&“Hello”;
return&function()&{&return&“Hello”};
如果函数将其他函数函数作为输入参数或者作为返回值,则称之为高阶函数。刚才我们已经看过了一个高阶函数的例子。下面,我们来看一下更复杂的情况。
[1,&2,&3].forEach(alert);
//&alert&弹窗显示“1"&
//&alert&弹窗显示&"2"&
//&alert&弹窗显示&"3”
function&splat(fun)&{
&&&return&function(array)&{
&&&&&&&&return&fun.apply(null,&array);
var&addArrayElements&=&splat(function(x,&y)&{&return&x&+&y&});
addArrayElements([1,&2]);
最爱纯函数
纯函数不会有其他的副作用,所谓的副作用指的是函数所产生的对函数外界状态的修改。比如:
修改某个变量
修改数据结构
对外界某个变量设置字段
抛出例外或者弹出错误信息
最简单的例子就是数学函数。Math.sqrt(4) 函数总是返回2。他不会用到任何其他心寒信息,如状态或者设置参数。数学函数从来不会造成任何副作用。
避免修改状态

函数式编程支持纯粹的函数,这样的函数不能改变数据,因此大多用于创建不可改变的的数据。这种方式,不用修改一个已存在的数据结构,而且能高效的新建一个.
你也许想知道,如果一个纯粹的函数通过改变一些本地数据而生产一个不可改变的返回值,是否是允许的?答案是可以。
在JavaScript中极少的数据类型是默认是不可改变的。String是一个不能被改变的数据类型的例子:
&&&var&s&=&"HelloWorld";
&&&&s.toUpperCase();
&&&&//=&&"HELLOWORLD"
&&&&//=&&"HelloWorld"
不可改变状态的好处
&&& •&& &避免混乱和增加程序的准确性:在复杂系统内,大多数难以理解的Bug是由于状态通过在程序中外部客户端代码修改而导致的。

&& &•&& &确立“快速简洁”的多线程编程:如果多线程可以修改同一个共享值,你不得不同步的获取值。这对专家来说都是十分乏味并且易出错的编程挑战。

软件事务内存和Actor模型提供了直接在线程安全方式下处理修改。
使用递归而非循环调用
递归是最有名的函数式编程技术。如果您还不知道它的话,那么可以理解为递归函数就是一个可以调用自己的函数。
替代反复循环的最经典方式就是使用递归,即每次完成函数体操作之后,再继续执行集合里的下一项,直到满足结束条件。递归还天生符合某些算法实现,比如遍历树形结构(每个树枝都是一颗小树)。
在任何语言里,递归都是一项重要的函数式编程方式。很多函数语言甚至要求的更加严格:只支持递归遍历,而不支持显式的循环遍历。这需要语言必须保证消除了尾端调用,这是 JavasSrip 不支持的。
惰性求值优于激进计算
数学定义了很多无穷集合,比如自然数(所有的正整数)。他们都是符号表示。任意特定有限的子集都在需要时求值。我们将其称之为惰性求值(也叫做非严格求值,或者按需调用,延迟执行)。及早求值会强迫我们表示出所有无穷数据,而这显然是不可能的。
很多语言都默认是惰性的,有些也提供了惰性数据结构以表达无穷集合,并在需要时对自己进行精确计算。
很明显一行代码 result = compute() 所表达的是将 compute() 的返回结果赋值给 result。但是 result 的值究竟是多少只有其被用到的时候才有意义。
可见策略的选择会在很大程度上提高性能,特别是当用在链式处理或者数组处理的时候。这些都是函数式程序员所喜爱的编程技术。
这就开创可很多可能性,包括并发执行,并行技术以及合成。
但是,有一个问题,JavaScrip 并不对自身进行惰性求值。话虽如此,Javascript 里的函数库可以有效地模拟惰性求值。
闭包的全部好处
所有的函数式语言都有闭包,然而这个语言特性经常被讨论得很神秘。闭包是一个函数,这个函数有着对内部引用的所有变量的隐式绑定。换句话说,该函数对它引用的变量封闭了一个上下文。JavaScript 中的闭包是能够访问父级作用域的函数,即使父级函数已经调用完毕。
&&&function&multiplier(factor)&{
&&&&&&return&function(number)&{
&&&&&&&&&&return&number&*&
&&var&twiceOf&=&multiplier(2);
&&&&console.log(twiceOf(6));
声明式优于命令式编程
函数式编程是声明式的,就像数学运算,属性和关系是定义好的。运行时知道怎么计算最终结果。阶乘函数的定义提供了一个例子:
factorial(n)&&&&&& = 1 if n = 1
&&&&&&&&&&&&&&&&&&&&&&&&&&& n * factorial(n-1) if n & 1
该定义将 factorial(n) 的值关联到 factorial(n-1),是递归定义。特殊情况下的 factorial(1) 终止了递归。
var&imperativeFactorial&=&function(n)&{
&&&&if(n&==&1)&{
&&&&&&&&return&1
&&&&}&else&{
&&&&&&&&product&=&1;
&&&&&&&&for(i&=&1;&i&&=&n;&i++)&{
&&&&&&&&&&&&&&product&*=&i;
&&&&&&&&return&
var&declarativeFactorial&=&function(n)&{
&&&&&&&if(n&==&1)&{
&&&&&&&&&&&&&return&1
&&&&&&&}&else&{
&&&&&&&&&&&&&return&n&*&factorial(n&-&1);
从它实现阶乘计算来看,声明式的阶乘可能看起来像“命令式”的,但它的结构更像声明式的。
命令式阶乘使用可变值、循环计数器和结果来累加计算后的结果。这个方法显式地实现了特定的算法。不像声明式版本,这种方法有许多可变步骤,导致它更难理解,也更难避免 bug 。
函数式JavaScript库
有很多函数式库:underscore.js, lodash,Fantasy Land, Functional.js, Bilby.js, fn.js, Wu.js, Lazy.js, Bacon.js, sloth.js, stream.js, Sugar, Folktale, RxJs 等等。
函数式程序员工具包
map(), filter(), 和&reduce()函数&构成了函数式程序员工具包的核心。&纯高阶函数成了函数式方法的主力。事实上,它们是纯函数和高阶函数应该仿效的典型。它们用一个函数作为输入,返回没有副作用的输出。
这些 JavaScript 函数对每一个函数式程序来说都是至关重要的。他们可以去除循环和语句,使得代码更加整洁。这些都是实现 ECMAScript5.1 的浏览器的标准,他们只处理数组。每次调用都会创建创建并返回一个新的数组。已存在的数组不会被修改。但是稍等,事情很不止于此。。。他们还将函数作为输入参数,通常是作为回调的匿名函数。他们会遍历将整个数组并且将该回调函数应用与每一项!
myArray = [1,2,3,4];
newArray = myArray.map(function(x) {return x*2});
console.log(myArray); // Output: [1,2,3,4]
console.log(newArray); // Output: [2,4,6,8]
除了这三个函数,还有很多函数可以扎入到几乎每一个函数式应用里:
forEach(),concat(), reverse(), sort(), every() 以及some().
JavaScript的聚范式
JavaScript当然不是严格意义上的函数式编程语言,这也促使了对其他范式的使用:
命令式编程:基于详细操作描述式的编程
基于原型的面向对象式编程:基于原型对象及其实例的编程
元程序编程:操纵JavsScript执行模型的编程方式。对元程序编程的一个很好的定义描述为“编程发生在您书写代码做某事的时候,而元程序编程则发生在您书写代码导致某事的解释方式发生变化的时候。
原文地址:
翻译:simei231, 祝青, 李中凯
您可能感兴趣的文章:
大家感兴趣的内容
12345678910
最近更新的内容
常用在线小工具}

我要回帖

更多关于 纯粹什么意思 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信