ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

第一章:高阶函数

第一章:高阶函数 高阶函数在这里其实是混用了过程和函数注意区分不同语境下的意思。主要内容了解为什么要引入高阶函数了解过程作为参数了解lambda表达式了解过程作为返回值代码很少主要以理解为主接受高阶函数过程参数过程返回值的概念。为什么要引入高阶函数我们已经可以看到在作用上过程也是一种抽象它们描述了对于数的复合操作但又不依赖于特定的数。例如(define (cube x) (* x x x))我们不是在定义某个具体的数的立方而是对于任意的数都能通过这个过程得到它的立方。我们渴望在一些高级的抽象上做事比起每次使用立方的概念用*我们肯定更希望使用(cube x)。人们的一个必然需求就是为公共模式命名建立抽象然后在抽象的层次上工作。过程抽象的是数而高阶函数作为一种更通用更一般也更抽象的模式将会抽象过程它允许过程作为参数。我觉得看一看代码还是有必要的能让我们有很好的参照点。过程作为参数过程可以作为参数传入函数中这有什么用先来看三个不同的求和函数(define(sum-integera b)(if(a b)0(a(sum-integer(a1)b))))(define(cubex)(*x x x))(define(sum-cubea b)(if(a b)0((cubea)(sum-cube(a1)b))))(define(pi-suma b)(if(a b)0((/1.0(*a(a2)))(pi-sum(a4)b))))上面定义了三个都具有求和功能的过程是不是很像抽象它们将求和逻辑和具体的项逻辑分离将计算项的过程作为一个参数抽取公共模式这是很多程序设计语言共有的原则只是做法不同。(define(sumterm a next b)(if(a b)0((terma)(sumterm(nexta)next b))))这里有两个过程参数分别是项和步长。从而上面三个过程可以用高阶函数重写为(define(identityx)x)(define(sum-integer-hofa b)(sumidentity a add1 b))(define(sum-cube-hofa b)(sumcube a add1 b))(define(pi-sum-hofa b)(define(terma)(/1.0(*a(a2))))(define(nexta)(a4))(sumterm a next b))我们还可以使用sum这个高阶函数来定义积分函数(define(integralf a b dx)(define(sum-fa b)(define(nexta)(a dx))(sumf(a(/dx2))next b))(*(sum-fa b)dx))我们已经看到sum求和函数的威力∑\sum∑在数学上那么流行。sicp在这里提供了很多有趣的例子都需要使用求和函数定义复杂的计算过程作为练习这里就不一一展示了。有一个例子很有趣它抽象了一个combine参数这个参数定义项之间的运算将加法从中抽象出来通过combine就可以定义求和连乘了。当然实际开发中通常需要根据需要选择合适的抽象层次不是越抽象越好但你不觉得很爽吗你还能抽象些什么。在上面的例子中我们将很多东西公共逻辑从函数体中拿出来作为参数等待调用者传递。**过程参数代表什么**如果是普通的参数类型比如数值应该代表了一个集合那么一个过程参数应该看成一个过程的集合而且是一个接受特定参数类型同时返回特定值的过程集合。过程参数比普通参数更有一种契约的味道或者说是一个接口。在Java中就有一种叫“函数式接口”的特殊接口它里面有且仅有一个方法。使用函数式接口的地方可以直接传入一个lambda表达式这和Lisp没有多少区别。思考请思考以下几个问题我们能用过程参数抽象什么不能用过程参数抽象的又是什么或者说什么东西不能仅仅通过过程参数来实现你能用抽象的语言来描述它们吗请思考关于后者那些不能使用过程参数表达的抽象。你需要Lisp语言为你提供什么来支持这些你想要但实现不了的抽象提示过程作为返回值就算回答不了这些问题思考本身也是有意义的当你清楚知道过程作为返回值的动机那么学习它还有什么难的。这里先剧透一下不能使用过程参数抽象的是那些需要运行时数据参与构成的抽象。什么意思简单来说所有函数都是预先定义的不管你将它用在哪里即使是过程参数追根究底是来自手写的定义。但是你有没有想过一个过程它在接受数据时才真正产生意义重获新生成为一个具有实际意义的计算过程。而不是像“普通函数”一样只是对传入的数据进行操作或者配合传入的过程对数据进行操作两者本质上就不同它们的功能也会大相径庭。lambda表达式lambda表达式也常被称为匿名函数lambda代表的还是过程本身就像一个数可以作为一个常量存在而不是生来就是一个变量和名字绑定。一个过程也可以没有名字就只是过程本身。lambda表达式是这样的(lambda (x) (* x x))(lambda (x y) ( x y))。(lambda(param*)body)lambda表达式非常重要。在lisp中它是过程的值形式。正如1218之于数值变量x一样。它表达的就是过程本身非常纯粹而不是附带一个名字。lambda可以在任何使用命名过程的地方使用就如同数值可以在任何使用变量的地方使用一样。lambda可以作为返回值返回这为过程作为返回值提供了基础当然也可以返回一个局部定义或者干脆就是全局定义外部引入的的具名函数。;传入a会返回一个“ a“函数(define(add-somethinga)(lambda(b)(a b)))(defineadd-1(add-something1))(add-12);return 3;这也是可以的(define(add-somethinga)(define(addb)(a b))add)let表达式用来定义局部变量。它是一个语法糖。(let(lacal-define*)body)(let((name1e1)(name2e2)...)body)实例(define(test-letx y)(let((sx(*x x))(cy(*y y y)))(sx cy)))为什么说let是一个语法糖它需要解释器在底层定义吗还是说可以使用现有的东西定义它其实长这样((lambda(name1name2 ...)body)e1 e2 ...)但你是无法手动定义它的定义它还需要使用额外的语言工具但实现原理就是如此。过程作为返回值将过程作为返回值将进一步增强表达能力。为什么需要这种能力既然我们可以使用define来定义过程和高阶过程为什么需要将过程作为返回值这是问题的关键也是回答过程作为返回值到底增强了什么表达能力。而在一般的编程语言函数不是一等公民里是没有这种语法的。这种能力能让我们完成一些非常离谱非常花里胡哨的操作这可是真正的Lisp特色。想让我们看看过程是怎么来的还能怎么来定义呗。相信你一定想不假思索的回答但是现在你面对的是Lisp一门神奇的编程语言如果说前面的语法对你来说不过是形式上的转变你甚至能在脑中大致构建一个解释器模型的话现在恐怕不行了。一个过程除了直接定义现在能由过程来生产。就像你定义一个立方过程就能源源不断生产立方数那么一个返回过程的高阶过程能让你源源不断地生产一个个过程。你甚至可以一次性创建一百个过程而这些“大白菜”原本需要你一个字符一个字符敲出来。同时它将被允许在运行时产生也就是说可以非常轻松地在运行时改变程序的行为。高阶过程的意义高阶过程的重要性就在于是我们能显示地用程序设计语言的要素去描述这些抽象是我们能像操作其他计算元素一样操作它们。一般而言程序设计语言总会对计算元素的可能使用方式强加某些限制。带有最少限制的元素称为第一级元素。第一级元素的权力包括可以用变量命名可以提供给过程作为参数可以有过程作为结果返回可以包含在数据结构中Lisp给了过程第一级元素的权力高阶函数是Lisp抽象家族的核心它将会变得伟大也将使计算过程这一抽象变得普通它会取代数据从此数据和过程将没有区别。练习sicp在这里安排了充足的内容让你理解和练习。包括折半寻根不动点连分式等。这都是一些有趣的数学概念特别不动点它可以用来求开根号。不动点作为一个抽象的一般的威力强大的过程想要发挥它的力量就必须使用高阶函数进行抽象而如何抽象不动点将是理解的重点。它没有作为练习而是作为实例出现在书中。这里省略了很多具体例子原因是我想快速过掉第一章。但练习还是很有必要的尤其是高阶函数抽象否则后面内容越精彩你越是看不懂。相信你和我一样一直都使用静态语言很少接触过动态语言比如JavaJavaScript对于过程作为返回值十分迷茫这很正常。下期预告下一期详细讲解不动点以及过程作为返回值的应用。
返回列表