从函数式的角度反思

我们都知道第一台电脑是ENIAC,但更前是什么?加法器,差分机,分析机等。还记得打孔卡吗?没错,最古脑的编程。为什么我提到这些?因为他们的变化,正是我们思维的应有的变化,但纵观各大技术论坛,哪怕是学校,公司,大部分都忽视了这个变化。


接着说,为什么加法器,分析机等,不叫做电脑?最重要的差别,存储。之所以叫“脑”也因为具有记忆能力。然而我不是来解释什么是电脑的,我提出的目的是让你看到,即使在第一台计算机诞生之前,仍然能够进行逻辑运算。人脑的记忆能力和逻辑能力是分开的,你即使记得 1 2 3 等数字,+-*/等符号,但这些记忆不是逻辑——你只是记住了,这并没有证明你了解他们的关系。计算机其实也是这样,运算器和存储器是很自然地分开的两个概念。


我们都是编程的,自然地我们经常接触存储技术,持久层等概念。嗯,我们认为掌握这些就能满足计算机的要求了。但事实是,这是非常表面,或者说这些根本没有解决问题。最近看过我帖子的人都应该知道,我编程思维已经发生很大改变。没错,我已经纵身投入到函数编程领域,特别是Haskell。我们都知道跟计算机是建立在大量的数学理论上,所以跟计算机交流最好就是数学。我们最常最广泛使用的指令式编程,却离数学非常远。


函数世界里的计算机


指令式与函数式对比,这是个久远的话题,好像函数式一直处于下风。我们经常说指令式对自然语言友好,函数苦涩难懂。但我并不谈那个,而是谈谈这个:在指令式和函数式的世界观中,他们认为计算机是什么?这是很少人会去思考的问题。在指令式的世界观中,很简单,计算机就是指令的集合,而函数式呢?非常特别,它认为计算机是一个IO函数(特别拿出Haskell,因为他是纯的)。我第一次接触Haskell的时候,看见main函数是IO函数时,也是一愣,然后脑中莫名冲动起来,因为函数式一直以来都是因为IO问题而遭人“歧视”。在这么纯的函数语言中,IO是如何处理呢?因为只有处理好了IO才能登上计算机的舞台。


我以前有提过这么一个观点:存储了的数据,若果“永远”不会被使用来输出,那就等于没有存储数据。也就是说我们之所以存储数据,是因为我们以后“一定”会输出它(不一定是直接输出)。函数式在这里也有相似的理解,用抽象的表达式表达就是IOin ==> function ==> IOout,也就是说计算机实际上就是IO,整台计算机的逻辑就是中间的function。多么美妙,输入与输出是一起的,函数是他们的桥梁,这与指令式的思维非常不同。在Haskell里我接触到了monad,这真是神奇的东西,因为是它实现了函数里的IO,函数里的IO不像指令中的,不是想在什么位置输出就在什么位置输出,它是集中输出的,你可以理解IO是一个值类型,这类值具有这样的特性IO+IO=IO,就像[]++[] =[]一样。它把输出理解成可运算的(组合),类似这样的效果:print "abc\n" + print "def" = print "abc\ndef" ,最后把合成的东西交给IO,就会输出你想要的结果。从现实世界走入计算机,经过计算机思考,再从计算机走出现实世界,这就是函数式思维。


上面就像我在介绍函数式一样,这是必须的,因为我们进行反思前,首先要了解函数的思维。接下来,首当其冲第一个要反思的是数据库,DB。(等等,回家后接着写)
[该贴被SpeedVan于2013-07-26 18:36修改过]
[该贴被SpeedVan于2013-07-26 19:15修改过]

等待下篇

这两日真够呛。。。好继续,正文:

若果把计算机看成函数,那会产生什么问题呢?显然是存储问题,因为函数并没有存储概念。我们做数学题,做逻辑题,做问答,做统计,这些都不存在存储概念。因为我们思考逻辑的时候,定义已经是确定的了。也正因为是确定的,才可以逻辑思考。试问一个变量,时而鸡,时而马,该怎么判断呢?if判断?if后再变化你怎么办(多线程问题)?DB,一个存储器的存在,怎样融入函数当中呢?

假设,我们对DB不会作出任何修改,那么则有f' = f DB,于是f'就是计算机本身IOout = f' IOin

假设,我们对DB会作出修改,那么还有f' = f DB么?我们都知道定义域任意元素x,都有值域中唯一确定的元素y与之对应,于是y = f' x = f DB x存在问题,DB的变化,致使函数不存在。那么把计算机看成函数的观点崩溃了?我们换个角度,先把DB分成两部分:管理数据的函数g 和 纯数据 v。然后我们把纯数据看作参数,即 f' = \v -> f.g v,而g v就是DB。把管理数据的函数g看成系统的一部分,把纯数据看作IOin会怎么样?没错,我们重新得到IOout = f' IOin。

其实对于函数来说,要解释DB最大的问题在于状态。函数不存在状态,所以计算机加入了一个特殊的东西:时钟。值+时间=状态,在可以忽视时序问题时,可以直接有值=状态。忽视时序问题,就相当于我们平时说的异步。若果单单使用锁,只是一种耗费资源手段。而我们看到无论是唤醒线程,还是延时读取,都是依赖时钟。

请想起我之前所说的机器,他们从无态到有态,最关键的部分就是时钟。其实我们谈到状态这个词的本身,就已经引入时间概念。而我们现在遇到很多函数式都不涉及时钟,是因为我们需要的是异步。

[该贴被SpeedVan于2013-08-02 01:10修改过]
[该贴被SpeedVan于2013-08-02 07:52修改过]
[该贴被SpeedVan于2013-08-03 16:02修改过]

2013-07-30 07:50 "@SpeedVan
"的内容
若果把计算机看成函数,那会产生什么问题呢?显然是存储问题,因为函数并没有存储概念 ...

我是这样看的:命令式编程实际就是一个状态机,通过命令让状态改变;而函数式则是无状态的,无状态则无副作用,都通过计算实现。

因此,在实践中,经常两者结合一起,比如大数据处理,经常有中间结果状态,这些都可以被缓存起来,这样就会提升性能和效率。

总体来说,现实世界是有态的,计算机世界是无态的,用计算机反映现实世界,估计有态和无态结合,关键就是程序员如何识别有态和无态。

2013-08-03 17:49 "@banq
"的内容
我是这样看的:命令式编程实际就是一个状态机,通过命令让状态改变;而函数式则是无状态的,无状态则无副作用,都通过计算实现。

因此,在实践中,经常两者结合一起,比如大数据处理,经常有中间结果状态,这些都可以被缓存起来,这样就会提升性能和效率。 ...

我不是从一个个指令执行的角度思考的,我是从整台计算机分割的角度思考的。若果做软件,则假设整个软件系统“已经做好”了,现在对它进行有逻辑性的切割,产生一个个模块,而这些模块与模块间肯定是无缝的,这些模块犹如水管游戏,这些模块拼接好后,水从高处入,低处出,而这些模块本身又满足通水。所以,我有这么一种设想,一个流畅的系统,中间是不具有状态的。若果中间存在状态概念,那么肯定可以通过monad向外推,推到跟IO同一层次。

经常地,我们通常在中间,加入一些状态变量,用来标记,但状态实际上只存在多线程中。因为单线程下,所有类似状态的变量,都可以通过函数代入,全部去掉。但我们过往写代码的时候却只是顺序考虑,根本没有并行思维,这大多数是由于框架的事务帮我们解决这些问题,但用事务来描述逻辑,本身就有问题。事务就像用水桶接着水管的水,然后运到另外一条水管倒水,如此循环。事务套在逻辑上,会产生很多没必要的消耗,让事务仅仅处理状态改变,这才是事务消耗最小化。

所以,我思想偏向了,将所有状态自然地推向存储器本身处理,而留下所有水管进行拼接。

其实我们心存怀疑的是“多线程”这个东西,这东西到底是什么东东,是不是他把本来简单的逻辑复杂化,如何让我们描述逻辑的时候,可以无视这个问题。
[该贴被SpeedVan于2013-08-04 04:25修改过]

特意注册回楼主

首先声明本人不懂haskell

非常同意楼主的关于时间这个因素的考虑

函数式本意就是表达式求值,状态都在表达式里了,常说的函数当一个表达式被写出来以后,就不会变了,同样的输入就同样的输出。但是副作用咋办?Monad就是副作用和函数的桥梁,不过这是一个单向桥,一个函数可以被“污染成”monad的表达式,但是一个monad表达式就只能继续转换为其他monad表达式了。这里monad是一个很讨巧的设计,所有的函数,都是纯的,可以组合,可以嵌套,可以高阶处理,但是应用的时候可以直接在monad里应用(被污染),最终程序就是对monad求值了。