2009年5月29日星期五

Side-Effect

近来说FP(函数式语言)很好很强大的人越来越多了,只是因为没需求,所以就一直没动力去学。也正是因为这样,我对副作用、引用透明之类的概念总是不甚了了。昨晚忍不住拽了一位童鞋聊了聊,算是有了点收获吧。

首先是引用透明,某童鞋的说法是:引用透明就是可以用函数的返回值代替函数的调用。

我觉得的这样的说法并不完整,于是写了这样一段代码问他:

int x;

int fa(int m, int n) {
  x = m + n;
  return x;
}

int fb(int z) {
  return x + z;
}

他说fa函数不算引用透明,因为用fa的返回值代替函数调用后,整个程序的运行结果会发生变化。可问题是,如果fb从来没调用过,且没有在其他地方读写过全局变量x的值呢?

于是试着总结一下,一个引用透明的函数,似乎应该满足以下三个条件:

  1. 只通过传值调用输入数据;
  2. 只通过返回值输出数据;
  3. 不且不能读取和修改函数外的任何其他数据。

如果一个程序中的所有函数都满足这三个条件,那么就可以保证所有函数的调用都可以用返回值来代替。

而所谓副作用指的是上述第三条。如此,“没有副作用即是不对环境产生影响”是一种没错,但是没说清楚的解释。

那么,“没有副作用即是没有变量的值发生变化”又是怎么一回事?以我的理解,这是一个混乱而错误的说法。

若我对引用透明的阐述没有错,那么所谓副作用就只是指针和作用域的问题。假如C语言中没有指针和全局变量(IO的部分另说),那么也可以说C的函数是引用透明的。

扯出变量,似乎只是命令式语言的习惯性思维作祟,因为在FP里是没有变量这种东西的。

假如在某一门FP里支持这样的语法:“int x=1”,这并非是“把整数1赋值给变量x”,而是“定义符号x代表整数1”,更像数学里的说法。即使有“x=x+1”这样的语法,也并不是“计算表达式并修改变量x的值”,而是“删除符号x与原有值的关系并重新定义一个”,内存中x原指向的1的值并没有发生变化,2被存到另外的地方去了。

事实上,FP中的“x=x+1”更有可能代表的意思是累加,即“x = (x + 1) = ((x + 1) + 1) = ...”

值只能被定义,不能被修改,岂不是又慢又浪费内存?又为什么要强调引用透明?仔细想想,却也不是没有好处的。

引用透明了,垃圾回收就容易了。可以定义无穷表,且定义时不需要计算其值,也就是所谓的延迟计算。编译器可以缓存一部分函数的运算结果,加速程序运行。无副作用,所以对并发友好,更容易发挥多核CPU的计算能力。

连我这种不会FP的人都能想到这么多好处,FP果然是种很天才的东西。

奇怪的是,除了emacs和唐凤的Perl6实现,怎么没见过用FP实现的其他东西?

没有评论 :