这段时间零零散散地看完了Haskell的基础部分。对于长期使用命令式编程的人来说,要理解函数式的思想还是很痛苦的,不过几天下来,还算少许有些感悟, 写下来以作备忘。
函数式编程是基于lamda演算(Lambda Calculus)的一种编程形式,最早由阿隆左.丘奇提出。基本学过计算机的人都知道,现在的计算机是基于图灵的图灵机而 设计的,用于解决逻辑推理和计算问题。而就在lamda演算被提出来不久,丘奇就有证明了它和图灵机有等价的演算能力,也就是说,图灵机模型能干的东西lamda演算也可以干。 这也就是函数式编程的理论基础。好了,关于上面的叙述我们只要牢牢记住lamda演算和图灵机是等价的就可以了,因为在初学阶段函数式编程的种种奇怪特点可能会让你在感觉上质疑这个论断。
好了,开始说Haskell。
首先,它没有变量,或者说全部变量都是常量。这是我开始最诧异的一点,你能想象满是final声明的Java代码么,完全不能!但是Haskell可以,因为它是函数式语言。 在函数式语言中,“函数”才是一类公民,基本所有的逻辑都是靠函数之间的运算和调用完成的。既然没有变量,那产生的中间结果如何存储哪?答案是函数调用的返回值,也就是栈上。 看起来有些奇怪,吃饱了撑得非得搞这么绕来绕去的方法干什么哪?首先,函数式编程嘛,让变量出风头还叫什么函数式编程。其次,它确实有一些好处,比如无副作用(side-effect)函数
如果一个函数是无副作用的,他至少包含两点:一个是函数的运行不会改变任何状态(常量确保了这点),另一点是对于相同的输入函数总是返回相同的输出。 Haskell大部分函数都是无副作用的,当然为了和真实世界相连,也有一些副作用的函数(比如处理输入输出),但会通过明确地表示把他们分隔开来。
相比于命令式语言,函数式语言中的函数更“纯粹”和“干净”,而且更接近数学中的定义,这也是它的一大特点吧。
无副作用函数有什么优点么?
首先,易于调试:因为函数的返回值不依赖于时间和场合,我们很容易地恢复运行现场,只要确定输入参数就好了,比起普通函数错综复杂的依赖关系,调试顿时容易很多。
其次,程序健壮性好:函数不会改变外部状态,也不会受外部状态的影响,如果逻辑正确,想写出bug百出的代码都难(编程中大部分bug隐藏在状态的相互影响和制约中)
然后,无副作用的代码也使惰性求值成为了可能。
在haskell中如果你运行下面的代码:
let a = 1+2+3+4
当运行完这段代码的时候,a的值并没有被算出来,只有a被真正调用,比如当
print a
的时候a才会被真正算一遍并输出到屏幕上。同学们想到了在截止之前赶实验报告的情景了么,就是这样的情景。
无副作用的函数是惰性求值的基础,如果一个函数的输出不受制于时间和环境,那么什么时候的计算结果都是一样的。
惰性赋值的好处是我们可以完成下面的操作:
let x = [1..]
print head x
上面的代码x声明了一个正整数集,并输出这个集合的第一个数(head)。按照常规做法,程序会先生成x集合,然后把第一个数字取出来。但是注意:x是个无限集合! 在有限运算能力的机器上第一步就会耗尽其所有的时间。但对于惰性求值的程序哪?因为在调用x的时候已经知道只取第一个值就可以了,所以程序不比再生成之后的元素了。
惰性求值的好处在于给了编译器一个优化的空间,编译器可以综合不同函数的行为来生成最优化的结果,并最终提高代码效率(但实际上大部分Haskell程序还是很慢的,但这至少在理论上有优化的空间不是么)。
另一方面,Haskell函数更接近于数学的函数定义,我们可以借助数学工具对代码进行调优,演算,甚至在理论上验证运行的结果。
另外对于编程人员来讲,函数式编程的特性使我们更加注重于要做什么,而不是怎么做。(比如上面的例子,函数式编程的思路是:来一份正整数集,然后给我第一个数。命令式编程的思路是: 生成一个循环,上界设为1,并取出循环的序号1)。
感觉没什么可以说的,既然函数作为第一类公民,则函数可以扮演任何变量所能扮演的角色,可以做输入,也可以做返回值,可以来回传递。由于函数式语言一切量皆为常量的性质, 所以不必担心像JS里面的赋值问题(见这里)。
以上是一个作为Haskell新手的粗鄙理解,难免有很多错误。希望大家能多提意见