为什么我要学小众语言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]]] |
从1到20中,找出任意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 程鹏的主要内容,如果未能解决你的问题,请参考以下文章
c语言是什么鬼?为什么我要学c语言?万能的c语言也有缺点?该如何高效学习c语言?
JVM上小众语言的苦苦挣扎——Clojure Tutorial 从入门到放弃