函数式编程与命令式编程区别

最近在看MIT的经典SICP(structure and interpreter of computer programming),第三章讲到了命令式编程和函数式编程的区别(点击这里看SICP第三章),在此我想总结一下函数式编程与命令式编程的区别。

实际上用过lisp的人必然会对函数式编程深有体会。深陷于命令式编程的人们可能会惊讶于SICP前两章做了大量实践,你确找不到循环和赋值,而全部是通过递归和迭代的方法实现的。没错,循环和赋值是命令式编程的东西。

函数式编程和命令式编程的最根本区别在于,函数式编程将程序看作数学函数的求值,通过lambda表达式,函数的递归迭代来编程,因此函数式编程避免使用可变的数据(变量赋值),往往将函数当作参数和返回值来实现迭代;而命令编程则将其看成状态机,通过状态的迁移(变量值的变化),来表示程序。归结为一句简单的话就是,函数编程不赋值,命令编程以来赋值。下面是个scheme的实例

;functional flavor
(define (acc_recur x)
(if (= x 1)
1
(+ x (acc (- x 1)))))

;imperative flavor
(define (acc_statemash x)
(define (while condition command)
(if condition
(begin
command
(while condition command))
'()))

(define result 0)
(while (> x 0)
(begin (set! result (+ result x)) (set! x (- x 1))))

result)

这是一个累加器笔者可以用参数100调用两个函数,都能返回5050,acc_recur用函数式的方法,acc_statemash用命令式的方法(在这里我用递归实现了一个while语句,读者可以忽略while的定义,因为命令式风格的语言往往都内建了while,当然读者也可以从中看出函数式语言的灵活与强大),而前者没有使用任何变量,读者可以从中领悟两者差别。

遵照函数式编程,函数调用没有副作用,没有变量只有常量。何为函数调用没有副作用?因为没有变量的赋值,函数式编程中的函数调用不会改变函数的内部状态(局部或全局变量),所以无论你怎样调用这个函数,其内部的运作状态都是一致的。打个不实际的比方下面这个scheme程序展示的是命令式编程的副作用

(define tmp 1)

(define (foo)
(cond ((= tmp 1) (set! tmp (+ tmp 1)))
((= tmp 2) (set! tmp (+ tmp 1)))
((= tmp 3) (set! tmp (- tmp 2)))
(else (display "error")))
tmp)

(define (inctmp)
(set! tmp (+ tmp 1))
tmp)

每次调用函数foo的状态内部状态都改变,第一次调用得1,第二次得2,第三次得3,第四次又是1……,而每次调用其内部状态都不一样。这样除非程序员特别注意,否则无法预计函数调用的结果,同时foo和inctmp两个函数调用顺序不一样,结果也不一样。由于函数式编程不赋值,所以变量即是常量(或者说没有变量),比如这里tmp就是1的代名词,tmp永远等于1,而到了命令式编程中,tmp只代表储存位置,他的值是变化的,在复杂的情况下容易造成混淆,产生bug,这些都是命令式编成的弊端,特别在大型分布式运算中,命令式编程必须考虑不同节点的变量的同步问题,而函数式编程,由于没有副作用,不需要考虑同步问题,函数式编程可以拿来直接用在分布式运算中,成为分布式运算中的明星。当然函数式编程也有弊端,大量依赖递归,虽然可以使用一些技巧,如尾递归等提高效率,但是相比命令式编程,抽象程度相对较高,效率较低,因此虽然函数式编程的历史很长,但是在大部分时间里它被当作玩具,只到现在随着硬件的发展,计算机运算速率提高,他的思想才被慢慢融合进现代的脚本语言当中。

ps: 并不是用lisp这些functional-flavored语言就一定只能用函数式编程(当然Haskell这样的纯函数式语言例外),而c,c++就只能用命令式编程,正向上面的实例一样,许多lisp的方言可以通过set! , set, setq等方法改变变量值,同时能用loop来模拟循环语句。而c/c++则可以通过函数指针,等方法来实现函数作为参数传递, 以及递归来模拟函数式编程。现在许多脚本javascript,python,ruby同时从lisp这类函数式风格语言及c/c++这些命令式风格的语言中摄取精华。毕竟这两种风格只是一种编程的抽象工具,没有最好的工具,只有最适合的工具。


本文短链接:http://www.54fox.com/?p=293

Comments

Popular posts from this blog

socket close shutdown函数区别

批量在文件头插入

hash表取模技巧