Haskell是个神码语言
以下全部内容归原作者德天蛙所有。如原作者由于意愿于别处登载此文或其他任何原因而需要将此文下架(对移网不感冒怎么却开始用起app语了),本博将会立即执行。如有任何对原作者造成之影响,博主意愿承担可能之补偿并先行致歉。基于此类考虑,本文不向博客首页推送。此处仅因为博主全本无意近期学习诸如Haskell语言而又倾慕原作者著文质量之一贯水准而作为留挡马克之用。
20130302_Haskell是个神码语言
Haskell语言是一种函数式编程语言,而且是具有lazy evaluation(懒求值)特性的函数式编程语言。一开始看见函数式编程概念,感觉很高深,很computer science,很有计算机科学范儿,但是,学了一点之后,才发现它偏数学味儿,不是十分贴近computer science,特别对于时间、空间复杂度一开始都是隐藏着的,不作为主要的特性传达给读者的。
我用两本Haskell书来入门,但到目前为止,只能算是入了扇大门,能编些简单的函数而已。这两本书分别是Real World Haskell和Learn You A Haskell For Great Good。前面这本书我读完了前3章,读到第4章半当中的时候实在读不下去了。后面这本书则目前看到Recursion这块,刚讲了快排算法。
Haskell语言的语法里面,函数调用是比较简单易懂的。比如,div 3 2就是对3作除以2的整数除法。第一个div就是函数,后面跟着的都是参数。参数可以用括号括起来,这样就能表达较为复杂的情况,例如嵌套的函数调用:div (add 1 2) 2。
然后复杂的一点的是类型,类型除了对象或数据本身的类型之外,还有type class(类型类属)。比如说,Int类型是整数本身的类型,而Ord(可作大小比较)则是类型类属(type class),类似于C#语言中的接口概念。一个Int一定是一个Ord,一个Ord未必是一个Int。
还有一种东西叫类型变量,其实就是把本来用于数据的变量概念用在类型上面。
为子把上面讲的三个概念放在一起看,我们看一下下面这个函数(效率并不高,时间复杂度O(n ** 2),只是作个例子):
lastSeveral :: Int -> [a] -> [a]
lastSeveral n [] = []
lastSeveral n (x:xs)
| n < length (x:xs) = lastSeveral n xs
| otherwise = x:(lastSeveral n xs)
> lastSeveral 1 []
[]
> lastSeveral 1 "abc"
"c"
> lastSeveral 2 "abc"
"bc"
其中,lastSeveral是个函数,它的输入是一个整数Int和一个列表[a],其元素类型用类型变量a表示,但是我们不限定a到底属于哪些type class,所以任意类型的列表都能支持。它的输出是另一个列表,其中的类型也还是a,也就是说,输入的如果是个整数列表,输出的也还得是个整数列表。
类型声明的下面,我们定义了lastSeveral的实现。具体实现这里谈起来有些长,至于时间复杂度的问题也是因为Haskell列表的实现特性所决定的(是个单链表),所以不详谈。相信看了书以后就能理解。
有趣的是,Haskell实现某些算法显得特别简单明了:
quicksort :: (Ord a) => [a] -> [a]
quicksort [] = []
quicksort (x:xs) =
let smallerSorted = quicksort [a | a <- xs, a <= x]
biggerSorted = quicksort [a | a <- xs, a > x]
in smallerSorted ++ [x] ++ biggerSorted
一个快速排序能只用数行代码就写出来。但是,反过来,有些在常见的命令式(imperative)编程语言(也就是绝大多数编程语言,包括C#、C/C++、Java、Perl、Python、Ruby、VB、JavaScript、ABAP等等)中,一些简单的程序放在Haskell里却很晦涩。例如将一个数组中的元素全部随机分布(或者叫“洗牌”[shuffle])。
import Control.Monad
import Control.Monad.ST
import Control.Monad.Random
import System.Random
import Data.Array.ST
import GHC.Arr
?
shuffle :: RandomGen g => [a] -> Rand g [a]
shuffle xs = do
let l = length xs
rands <- take l `fmap` getRandomRs (0, l-1)
let ar = runSTArray $ do
ar <- thawSTArray $ listArray (0, l-1) xs
forM_ (zip [0..(l-1)] rands) $ \(i, j) -> do
vi <- readSTArray ar i
vj <- readSTArray ar j
writeSTArray ar j vi
writeSTArray ar i vj
return ar
return (elems ar)
?
*Main> evalRandIO (shuffle [1..10])
[6,5,1,7,10,4,9,2,8,3]
这个摘自http://www.haskell.org/haskellwiki/Random_shuffle的程序,利用了一些库中的机制来实现shuffle,包括把xs这个链表转换成数组listArray,然后用thawSTArray、readSTArray和writeSTArray来操作数组中的数据。这个过程看上去类似于命令式编程语言而不像函数式编程。简洁性也不是特别好。
总之,感觉并不是每个函数式编程的程序都容易理解,也并不是每个都很简洁。觉得,函数式编程可能对某些程序来说是可以写得比命令式编程更偷懒,但不是所有程序都行。作为程序员来说,还是在恰当的场合下,把它的思想用在命令式编程语言中,才会产生较好的效果。需要注意的是Haskell的递归嵌套层数几乎没有限制,但大多数编程语言是有限制的,因此还是不能完全照搬套用。