8. [电台文本] 化整为零的方法:函数

在编程语言中,借鉴了大量数学上的术语,但是数学其实与编程的关系不大。可能有很多人会说计算机是数学的一部分,那这个人就是个大忽悠,或者他误解了数学,也误解了编程。与计算机比较相关的一门数学是《数理逻辑》,这个所有计算机系的学生都学过这门课,如果你不是读计算机语言的博士,仅仅是通过编程混个饭吃,学不学《数理逻辑》应该是一点影响也没有的,至于像《微积分》这种数学,与编程的关系就更远了。除非你特别想研究数学,我才建议你去学《微积分》,如果你仅仅是想学一门编程语言,能简化自己的工作,你知道极限、微分与积分,几乎是没有太大用处的,除非你是从事数学相关的行业。

接下来就说这个函数了,函数是一种数学用语。在编程中,和很多概念一样,函数在不同的语境中,含义是不相同的。同样的情况也发生在诸如接口、代理等概念上,不同的编程语言,对此有不同的定义。函数有很多的名字,比如有人称之为方法method,有人称之为事务,有人称之为子程序(subroutine),有人称之为程序(procedure),现在国人比较多的称之为函数。但是这个函数又与函数式编程中的函数有点不同。这一期前面部分的函数与函数式编程没有关系,后面部分的函数与函数编程有关系。如果仔细考量的话,用较真的方法来讨论,子程序没有返回值,函数有返回值,对我来说,这不重要,意思大差不差就可以。

在这一篇中,我想讲的是化整为零的方法,我觉得叫子程序也许比函数要好。因为一说函数,总让人联想到数学上的函数,有自变量什么的,但是在编程中,很多函数并没有自变量,仅仅是一种把一大坨代码整理的能让人读懂的技术。这种方法对计算机没什么用处,对程序员有用处,对计算机来说,反正它也不读源码,反而函数太多,还会有相关的开销。但是对程序员来说,如果代码太多了,一眼望不到边的那种,就必须要通过某种手段来切分开来,函数有的只有一行,有的有数十行,最好不要超过数百行,如果达到了数百行,那就再切分一下。

除了容易阅读以外,函数最重要的功能实际上是重复使用。程序员一定要知道的最大的道理是:你写过的代码,前人一定写过,你得想个办法把人家的代码抄过来。现在主流的编程语言都有自己的包管理软件,比如Python有pip,Ruby有RubyGems,nodejs有npm……包管理软件是干什么的?简单来说,就是为了让程序员抄代码不用搜索,直接用命令来抄。虽然包管理软件不是函数,但是其实质的内涵,就是函数。

接来下,我来考证一下编程语言函数的小历史:

我要先声明一下,我觉得函数这种思想,已经是人类的古老智慧了,如果一项工程很复杂,那就分成一小块一小块的,用在编程中,也合情合理。中国不是有一个成语叫“分而治之”么?比如秦始皇统一六国,用的策略就是合纵,这几个国家单独比起秦国来,是比较弱小的,但是用公孙衍的方法,几个国家联合起来,还是有机会的。秦国采纳的方法就是让使六国不能团结一致,造成六国彼此的内讧,这样的话,一个一个打,最后全干死。在编程中,我所知道的最先采用这种方法的是1949年的EDSAC计算机采用了这种编程方法。只是当时没有使用函数这个名字,函数function这个名字,在数学中是1673年被莱布尼兹首先采用的。

大家可能对1673年没什么概念,作为纵向对比,在这个年代里,莱布尼兹的老对手牛顿已经把经典力学建立起来了,中国大清才刚建立起来,康熙还在当皇帝。既然说到了康熙,突然想起来康熙也用了函数这种分而治之的方法平定了三藩,一起打三个比较难,就一个一个打,最后完成目标。

在学术上,1930年发明的lambda演算也使用了函数的概念,只是这个函数的概念更多的是侧重于函数编程那个方面。

在使用函数的时候,语言设计者已经把坑给我们填好了,如果你想设计一个新的编程语言的话,就要考虑这样一个问题,当使用者调用了某个函数,函数执行结束以后,要返回到调用的位置,如果返回的时候,该地址被其它的函数给修改了,那应该如何避免这种情况发生呢?比如有个函数X,函数X在执行的时候调用了函数Y,没想到函数Y把函数X的地址给改写了,那函数Y运行结束后,把控制权给函数X,函数X在这种情况下就SB了。X的运行环境已经被X自己调用的Y给破坏了。

这种情况已经填坑了,就是用栈来解决,大家可以随便找个资料学习一下。

如果你要设计一门新的编程语言,还有一个函数的方面要考虑,就是函数能不能当作参数来传递,能不能把函数赋值给一个变量,能不能让函数成为一个返回值。这就是所谓的函数是不是First-Class,头等公民的问题。

函数是不是头等公民,或者是二等公民的问题,是许多编程爱好者争论不休的问题之一,一般来说,只要在网上吵架吵到了函数式编程或者值级编程,就一定要拿出这个来作为子弹搞一下对方。其实这有个规律,越后期的语言,越支持函数作为头等公民,这是一个普遍的认识规律。就像大哥大时期的手机重1公斤,得有个人专门提着,还没有相机的功能,但是现在的手机,基本都有相机的功能。在编程语言中也是如此,后人越来越注意到函数作为头等公民确实有优势,于是就开始支持了。

这是一个过程,以很多爱好者喜欢的Lisp为例,Lisp有许多的方言,最著名的有两个,一个是Scheme,一个是Common Lisp,当然了,有人认为Ruby也是Lisp的方言之一,这个没必要争论。Scheme在设计头等函数的时候,就出现了一个bug,Scheme使用的是动态作用域,这被证明是有问题的,后来者Common Lisp就修正了这个bug,使用的是词法作用域的头等函数。

以应用更广的Python为例,在Python的早期,如果我没记错的话,在2.5之前,Python还没有安装相机,Python的匿名函数只允许表达式作为函数体,到了后来,越进化越强大。当然了,关于Python的lambda是好还是不好,也一直有争论,有说好的,有说不好的。Python是个开源的项目,如果你觉得不好,你就加入Python编程社区,混成大佬,改变Python。如果你不想改变Python,那就新创建一个新的语言。

这句话非常流行,如果你觉得黑暗,你就加入它,并且改变它。这是一种非常流氓的逻辑,如果这个逻辑成立的话,那大清非常腐败,为什么孙中山不考科举,加入大清,改变大清呢?而是直接推翻大清,建立中华民国呢?在编程语言上也是如此,你觉得不爽,你就自己去搞一个编程语言,自己去搞一个框架。我举个例子吧,写Java的人都应该知道一个叫Spring的框架,在此之前 ,大家都用另外一个框架写代码,那个框架叫Java EE,又大又慢又难用。于是,一个叫Rod Johnson的音乐系的学生,觉得太难了,他就写了自己的框架,名字叫Spring。

前面的部分与函数编程没什么关系,现在我要讲点有关系的了,函数编程中的函数,这里的函数与C语言中的函数有区别。我觉得这个翻译啊,C语言中不如叫子程序,这样不容易混淆。

现在函数编程已经越来越被接受了,现代的编程语言对函数式编程都做了不同程度的支持,例如:JavaScript, Coffee Script,PHP,Perl,Python, Ruby, C# , Java 等等,我来讲一下函数编程的历史吧,这样大家与C语言这咱值级编程,Java这种面向对象编程有个区分。

先来讲计算模型,这是科学家做的事情,大家应该熟悉图灵机,现在咱们用的就是图灵机的计算模型,除了图灵机这个计算模型,还有两个:一个叫递归计算模型,还有一个叫lambda计算模型。这个就是数学家的事情了,在1900年,离现在已经120多年了,那时候还没有计算机,1900 年 David Hilbert希尔伯特提出了23个问题, 当时慈禧太后正被外国人吓的跑到了西安,这23个问题,还有7-8个没完全解决,有3-4个完全没解决,如果听电台的各位,能解决部分问题,那你就是在历史上留下名字的人。

这23个问题中的第10题,是有关数论的题目,能否通过有限步骤来判定不定方程是否存在有理整数解,这个问题已经被苏联的数学家给解决了,答案是否定的。我们不能够通过有限步骤来判定不定方程是不是存在有理整数解。在解决这个问题的过程中,有无数的数学家投入其中,他们搞出来了一个计算模型,这个计算模型就是lambda计算模型,当时他们并没有意识到这玩意可以造计算机,其中有歌德耳,丘奇,图灵等科学家都在这些模型上面投入了巨大的精力。并且,他们的本意并不是为了造一台计算机,仅仅是学术方面的研究。

后来,科学家已经证明这三种计算模型是等效的。多说一句,有些突破性的创新,并不是咬咬牙,靠牺牲一批人就能成功的。比如这些计算模型的发明,最初谁也没想到要造计算机,比如前面说的希尔伯特,他跟慈禧太后是同时代的人,希尔伯特成为举世瞩目的数学家的时候,慈禧太后还没下定决心搞议会,而英国搞议会制度的时候,大清还没建立,当时朱元璋还在当皇帝,这种差距,实在是过于巨大。等到这三种模型的数学理论都已经搞定以后,科学家才开始造电脑。

起初的时候,在欧美做电脑的流派有不少,其中有个造电脑的流派就是lamda模型流派,这就是函数式编程的老祖宗。

现在我们可能见不到函数式计算机了,在20世纪80年代的时候,市场上出现了不少有美国自主知识产权的电脑,这些电脑Intel的机器不同,它们设计出来就是为了跑函数编程语言的,比如MIT Lisp机器项目,德州仪器的Explorer(这个名字有点像微软的浏览器,不过,真就是这么个名字),还有施乐的电脑。这些电脑最后虽然没有取得成功,但是也给我们留下了很多宝贵的遗产,比如大家都用过的鼠标,还有几乎所有高级点的语言都支持的内存垃圾收集,最初都是在函数式计算机上实现的。

好东西就要相互借鉴与抄袭。所以,建议大家有空的时候学习一下函数编程,学了函数编程以后,你会神奇的发现,如果你是写Java的,以前你严重依赖设计模式,而这些设计模式,一旦使用了函数编程,不少设计模式其实没什么用。

当然,这不是我夸函数编程很伟大,函数编程也有自己的问题,如果没有问题的话,早就成功的不像样了,显然,没有取得想象中的成功。如果要成功,早就在有函数机器的年代成功了,也不用等到现在。建议一直用C语言这类语言写值级编程的同学,研究一下函数编程;如果一直研究函数编程的,就研究一下值级编程。两者都有优点,不必厚此薄彼,前几年,我一个同事叫小潘,要实现这样一个简单的功能,我们是通信行业,经常要使用环形缓冲区作为数据结构来存放通信中发送和接收的数据。环形缓冲区是一个先进先出的循环缓冲区,可以向通信程序提供对缓冲区的互斥访问。这么个小数据结构,用C语言一会儿就能实现了,他用的是Haskell这种纯函数编程语言,做的非常的辛苦。

再加上他那种程序员的性格,你们说不行,我就一定要实现给你们看。这个小潘可不是菜鸟,是Haskell高手,只是有那么一段时间,他中了函数编程的毒,觉得可以用函数编程搞定一切,于是他有那么一段时间,天天推广函数编程,最后中的毒下去了,找了女朋友,慢慢就好了。如果那个环形缓冲区用C语言的话,也就是100多行代码的事儿,他硬生生的搞出了好几个文件,最后性能还不达标。因为我们要跑在路由器上,代码写的丑点没什么,但是一定要跑的够快。

时间到了,我发现我还想讲一个东西,差点漏了,就是递归。函数的递归,这里的函数所代表的意义是方法。

递归计算模型也是三种计算模型之一。以前我在电台里讲过巴贝奇的机器,就是人类历史上第一个女程序员——拜伦的女儿Ada那一期,递归计算模型的要点是:循环运行,一次迭代的结果将成为下一次迭代的输入,每个函数前后相继,遵循相同的规则。

绝大多数编程语言支持函数的自调用,在这些语言中函数可以通过调用自身来进行递归。计算理论可以证明递归的作用可以完全取代循环,因此有很多在函数编程语言(如Scheme)中用递归来取代循环的例子。大家写个斐波那契数列就知道我说的什么意思了,所有的教科书上,一说递归,就把斐波那契数列给拉出来,一点创意都没有。

好,这三个计算模型就说到这里吧,只是蜻蜓点水的提了一下,希望能开阔你的视野。

5 2 投票数
文章评分
订阅评论
提醒
4 评论
最旧
最新 最多投票
内联反馈
查看所有评论
懒懒龙
2 月 前

栋哥您的电台,是先有文字稿,还是先有音频?感觉是后者,那文本是手敲的还是语音识别的?识别的话用的什么软件?谢谢

CJ.zhang
2 月 前

栋哥,我摸索使用clash过程中,好像发现了这个GitHub 项目幕后高明的变现路径,分享与栋哥探讨。中美播客商业模式就不同,我从clash设计理念发现与美国播客模式遵循一样模式,不向C端收费,而是通过向机场收取API等服务收费。 Ventoy你用过吗?我也分析该项目变现模式,都是针对C端免费,向B端收费。想听听栋哥看法

最后由CJ.zhang编辑于2 月 前
生命一号
2 月 前

栋哥说的Rod Johnson的演讲https://www.youtube.com/watch?v=UKx9YkOF03Q&t=112s

4
0
希望看到您的想法,请您发表评论x
()
x