Scala与Haskell的严谨优雅性比较
函数语言主要优点是秉承数学的严谨性与可推导性,该文比较了纯函数语言Haskell在代数方程上与Scala语言的不同性,突出了Haskell纯函数语言的特点。
Haskell for all: Algebraic side effects
初中或小学数学中我们都学过方程式:
f * (xs + ys) = (f * xs) + (f * ys)
左边的方程等同于右边的方程,而在函数语言中也是秉承这种交换性的,假设我们做个符号替换:
1. 使用 Haskell的map函数替换数学的乘号。
2. 使用Haskell的 ++ 操作符替换数学的加号。
这样上面的方程式就变成了Haskell的等式:
map f (xs ++ ys) = (map f xs) ++ (map f ys)
也就是说,将集合xs和ys串联起来后,然后基于串联后的集合使用名为f的map函数,其结果等同于:对xs和ys每个集合单独使用名为f的map函数,然后串联这两个结果。
使用Haskell REPL运行效果如下:
|
上述代数效果并不是在每个号称函数语言中都会有,其他语言使用这样的方程式会产生副作用,无副作用是函数语言引以为傲的主要特点,因为其他语言不会像 Haskell那样等式两边产生相同的顺序效果,如[3,4,5,6],有的可能是[3,5,4,6],这样就没有代数方程的严谨性了。
让我们使用混合式语言Scala实现看看:
|
使用串联与map函数先后不同就会导致结果顺序不同:
|
第一行中,两个集合首先串联,然后针对串联结果使用map函数,结果是,首先打印出"!"和"?",然后是f函数对每个元素的结果,打印出4次"*";而在第二行,对xs集合每个元素使用f函数后,在同样对ys采取同样操作,打印的结果和前面顺序就不同了,不是"!?",而是"!*","?"符号夹在4个"*"中间了。
这表明,语句的前后顺序在Scala会导致不同的程序结果,这种语句很显然没有代数方程的特点和严谨性,这样的语句是无法可推导的,经不起推敲的,有副作用的,不是可交换性的associative。
那么Scala是不是没有办法解决呢?原文给出了Scala的复杂解决方案:
|
测试结果:
|
现在函数的顺序是幂等的。
对比一看,Haskell的优雅与简单一目了然,很多人怀疑Haskell在消除副作用以后会破坏数学的优雅性,很显然这不是真的。