为什么我要学小众语言Haskell? by 程鹏

Posted 终端团团

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了为什么我要学小众语言Haskell? by 程鹏相关的知识,希望对你有一定的参考价值。

Haskell号称是最“纯粹”的编程语言。它长期混迹于 TIOBE 编程语言排行榜的50名上下,毫无疑问是相当小众的。略带清高,又十分小众,这就是典型的“文艺”标签啊!这样一门语言,是否值得花时间学呢?


自古以来,就有“学以致用”和“学以悟道”两派。前者暗暗透着儒家的入世理念,而后者多少有点道家的出世风骨。有人说,中国人在得志的时候往往是儒家,在失意的时候往往是道家。最后《菜根谭》出世,说“思入世而有为者,须先领得世外风光,否则无以脱垢浊之尘缘;思出世而无染者,须先谙尽世中滋味,否则无以持空寂之后苦趣。”我深以为然!


举个例子,看看学Haskell如何出世,又如何入世。


排列组合是经常在面试中考查的题目。用Haskell来写实在是太简洁了。


Haskell code

permutation _ [] = [[]]

permutation 0 xs = [[]]

permutation n xs = concatMap (\x -> map (x:) $ permutation (n - 1) (delete x xs)) xs

combination _ [] = [[]]

combination 0 xs = [[]]

combination n (x:xs)

| n == length (x:xs) = [x:xs]

| True = map (x:) (combination (n - 1) xs) ++ combination n xs


如何使用呢?也很酷。


执行排列函数的示例

*Main> permutation 3 [1,2,3]

[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

执行组合函数的示例

*Main> combination 2 [1,2,3]

[[1,2],[1,3],[2,3]]

3个数里面列出2个数的组合,然后对2个数的每个组合做排列

*Main> map (permutation 2) (combination 2 [1,2,3])

[[[1,2],[2,1]],[[1,3],[3,1]],[[2,3],[3,2]]]

120中,找出任意2个数相加等于10的组合

*Main> filter (\x -> sum x == 10) (combination 2 [1..20])

[[1,9],[2,8],[3,7],[4,6]]


在Haskell的奇异世界里面游历了一趟之后,看看怎么回来入世。先用Python写一遍。


Python code

def delete(xs, x):

_xs = xs[:]

_xs.remove(x)

return _xs

def permutation(n, xs):

if xs == [] or n == 0:

return [[]]

return reduce(lambda a, b: a + b, map(lambda x: map(lambda px: [x] + px, permutation(n - 1,delete(xs, x))), xs))

def combination(n, xs):

if xs == [] or n == 0:

return [[]]

if n == len(xs):

return [xs]

return map(lambda px: [xs[0]] + px, combination(n - 1, xs[1:])) + combination(n, xs[1:])


Python代码和Haskell的大致上可以做到一一对应,但是其中也有一些微妙的区别。


传统的静态语言规定要在代码中显式地描述类型,比如此例中,n应该是一个整形,而xs应该是一个列表。静态语言的这个要求是为了编译时核对类型,以便检查出编码中的类型错误。Python和Haskell在形式上都没有对参数的类型进行描述,这两者在实质上却是不同的。Haskell也是静态语言,但因为它语法上更纯粹,大多数情况下可以自动推导出类型,不需要程序员显式描述。Python是动态语言,它天生缺乏编译时类型检查,这个有利有弊,牺牲了类型安全,但是换来了运行时动态修改类型的能力。


另外,用Python写的时候,额外编写了一个delete函数。这是因为列表的内置方法remove()返回的是None,也就是说并没有返回修改后的列表,导致没法直接写成permutation(n - 1, xs.remove(x))。其实,让remove()返回修改后列表是很简单的,也不会额外增加开销,因为返回的是引用。Python为何如此设计,我还没有查到原因。但可以猜想的是,Python一直以来秉持实用至极的设计原则,糅杂了各派编程思想,千头万绪之下,难免遗漏一些缝隙。所以它虽然引入了map、reduce、lambda等函数式零件,但在配套设计上仍有改进空间。


如果没有之前Haskell的过渡,我想我很难一下子想出Python的这种写法,也很难对Python语言的某些细节有更深入的领悟。


最后,用C++入一次世。


C++ code

template <typename input_iterator>

vector<vector<typename iterator_traits<input_iterator>::value_type>> permutation(unsigned int n,input_iterator begin, input_iterator end)

{

typedef iterator_traits<input_iterator>::value_type value_type;

if (begin == end || n == 0)

return vector<vector<value_type>>(1, vector<value_type>());

vector<vector<value_type>> ys;

for (input_iterator it_x = begin; it_x != end; ++it_x)

{

vector<value_type> xs(begin, end);

xs.erase(std::remove(xs.begin(), xs.end(), *it_x), xs.end());

vector<vector<value_type>> pxs = permutation(n - 1, xs.begin(), xs.end());

for (vector<vector<value_type>>::iterator it_px = pxs.begin(); it_px != pxs.end(); ++it_px)

it_px->push_back(*it_x);

ys.insert(ys.end(), pxs.begin(), pxs.end());

}

return ys;

}

template <typename input_iterator>

vector<vector<typename iterator_traits<input_iterator>::value_type>> combination(unsigned int n,input_iterator begin, input_iterator end)

{

typedef iterator_traits<input_iterator>::value_type value_type;

if (begin == end || n == 0)

return vector<vector<value_type>>(1, vector<value_type>());

if (n == std::distance(begin, end))

return vector<vector<value_type>>(1, vector<value_type>(begin, end));

vector<value_type> xs(begin, end);

value_type x0 = xs.back();

xs.pop_back();

vector<vector<value_type>> cxs1 = combination(n - 1, xs.begin(), xs.end());

for (vector<vector<value_type>>::iterator it_cx = cxs1.begin(); it_cx != cxs1.end(); ++it_cx)

it_cx->push_back(x0);

vector<vector<value_type>> cxs2 = combination(n, xs.begin(), xs.end());

cxs1.insert(cxs1.end(), cxs2.begin(), cxs2.end());

return cxs1;

}

为了和Haskell代码对应起来,这里没有用最优化的方法来编写C++代码。比如,没有使用C++最新版的新特性lambda表达式。因为C++的lambda表示式还要描述参数类型,而这里又是泛型参数,写出来要比Haskell和Python的长得多,嵌入这种复杂的语句,反而增加了阅读困难。另外,考虑性能的话,其实不用调用erase()、pop_back(),引用区间时对末尾减一即可。


但是可以看到有了Haskell的铺垫,C++能够比较清晰地做到支持泛化参数。


对比Haskell和C++的代码,可以看到C++为了同时兼顾类型安全和泛化所付出的代价,这也是C++难以精通的原因之一。


入世之道,尽在出世之中,我想这就是Haskell给我的最大收益吧……



以上是关于为什么我要学小众语言Haskell? by 程鹏的主要内容,如果未能解决你的问题,请参考以下文章

Haskell趣学指南

c语言是什么鬼?为什么我要学c语言?万能的c语言也有缺点?该如何高效学习c语言?

JVM上小众语言的苦苦挣扎——Clojure Tutorial 从入门到放弃

代码札记The Day I Learned Haskell 1

为什么开发人员喜欢函数式编程?

haskell简明入门