王垠github(王垠:不再推荐 Haskell_IT新闻_博客园)
原文链接
[收藏]
« »在之前的一篇博文里 ,我推荐从函数式语言入手掌握程序语言 。推荐的两种语言是 Scheme 和 Haskell 。可是出于多种原因 ,我必须告诉大家 ,我已经不再推荐 Haskell 。这里的原因比较深入 ,可能不容易说清楚 ,所以只简述一下 。如果有异议的话 ,可以来信跟我讨论 ,这样也可以帮我理清思路 。
先说说之前推荐 Haskell 的原因吧 。推荐它其实是因为是它的类型关系较 Scheme 清晰 ,并且有模式匹配等方便的功能 。可是类型系统和模式匹配 ,却不是 Haskell 所专有的 。其它的一些语言 ,比如 OCaml 和 Racket 也有很方便的模式匹配和能力相近的类型系统 。
现在停止推荐 Haskell ,其实是出于很多原因的积累:
1. 类型系统过于复杂
最开头的时候 ,Haskell 使用的是普通的 Hindley-Milner 类型系统(HM 系统) 。使用这种类型系统的原因是因为程序员不需要写任何类型标记(typeannotation)就可以“静态 ”的确保类型的正确 。可是这样做的代价是,这个类型系统表达能力太弱 。很多程序需要拐弯抹角的绕过这个类型系统的种种限制才写得出来。比如 ,Haskell 的 sum type 导致 constructor 的非常麻烦的多重嵌套 ,这我已经在一篇英文博文里面比较隐晦的批评了一下 。显然 HM 系统灵活性太差,所以 Haskell 内部后来引进了 SystemFw 。可是这些系统发展了好多年 ,还是不能解决问题。到现在 ,你仍然会在 Haskell 里面遇到莫名其妙的限制 。你觉得程序应该编译通过 ,可是它就是编译不过(比如我这篇英文博客所述) 。究其原因 ,其实是类型系统有问题 ,而不是程序员的思路有问题 。
有的 Haskell 程序员可能会反驳 ,说是因为我不能理解 Haskell 的类型系统 。那么我可以告诉你 ,我不但实现了 Haskell 和 ML 所用的 HM 系统 ,而且实现了比 HM 还要强大的 MLF, intersection type 等类型系统 。Haskell 推导不出来的类型 ,我的系统可以推导出来 。所以我说的话其实是出自第一手的依据 。
2. 参数和返回值的类型标记很有必要
与 Haskell 同门的 SML 和 OCaml 的类型系统也有类似的问题 ,甚至更加严重(比如 ML 有value restriction ,导致不必要的约束和困惑) 。但是很多“常规语言 ” ,特别是像 Java,C++ 等需要类型标记的语言,却没有这个问题 。很多人喜欢 Haskell 都是因为用它可以“不写类型标记 ” ,可是现在呢 ,最好的 Haskell 程序员都是先把类型写下来,才开始写函数 。一来这样思路清晰 ,你知道这函数要处理哪些类型的数据 ,你就明确的把它写下来 ,以后再来看 ,或者给其他人看 ,都一目了然 。二来是因为 Haskell 的类型系统由于加入的一些“不可判定 ”(undecidable)的扩展功能 ,有时候已经无法推导出类型了 。而给函数的参数和返回值加上类型标记之后 ,就可以轻松推导出类型。所以你看到 ,给参数和返回值加上类型标记 ,不管是对人还是对机器 ,都有好处 。所以经过我一学期的研究得出的结论是 ,HM 系统的类型推导 ,其实是多此一举 。
不过需要注意的是,函数的局部变量 ,其实是不需要类型标记的。比如在 Java 程序里常见的:
List<String> ls = newArrayList<String>();
这样的赋值语句 ,其实是没必要在左边加一个类型标记的,因为右边的类型我们知道 。在这一点上C++11 的 "auto" 是一个正确的方向 。比如在 C++11 里 ,你可以写:
auto ls =newArrayList<String>();
这种类型推导不难做 ,基本就是一个抽象解释器 。我给 Python 做的PySonar类型推导系统里面就实现了这样的功能 。
对任何语言 ,具体是哪些地方有必要加上类型标记呢?其实有一个很简单的方法来判断:观察信息进出函数的“接口 ” ,把这些接口都做上标记 。直观一点说 ,函数就像是一个电路模块 ,只要我们知道输入和输出是什么 ,那么中间的导线里面是什么 ,我们其实都可以推出来 。类型推导的过程 ,就像是模拟这个电路的运行 。这里函数的输入就是参数 ,输出就是返回值 ,所以基本上把这两者加上类型标记 ,里面的局部变量的类型都可以推出来 。另外需要注意的是,如果函数使用了全局变量 ,那么全局变量就是函数的一个“隐性 ”的输入 ,所以如果程序有全局变量,都需要加上类型标记 。
3. “纯函数式 ”并不是好主意
我最近常常跟同学开玩笑 ,说“纯函数式 ”语言是什么意思 。“纯函数式 ”语言是用来描述这样一个世界的 ,在这个世界里 ,所有的东西都是“有线 ”的(wired) 。不存在 3G ,4G ,不存在 wifi ,收音机 ,卫星电视…… 所谓的 monads ,其实就是这个布满电缆的世界里的“接线盒 ” 。
Haskell 编程之麻烦 ,就是因为这些电缆。你必须小心翼翼的把它们接在一起 ,安排好 ,否则就会有各种问题 ,甚至绊到脚 。连生成随机数这么简单的事情,你都得学会使用各种各样的“随机数 monads ” 。这是因为我们需要记录随机数发生器的“状态” ,所以随机数 monad 输入一个随机数发生器 ,返回一个随机数以及一个新的随机数发生器!想一想,在 C 语言里面 ,你只需要一个全局变量或者函数内部的 static 变量来记录随机数发生器的状态。到底是谁简单 ,谁复杂?我想你可能已经意识到 ,全局变量其实就是 wifi!
Haskell 的支持者常说 ,纯函数的语言容易“推理 ” ,容易确保程序的正确 。因为它的程序就像“数学的函数 ” ,给同一个输入 ,就会得到同一个输出 。这叫做“referentialtransparency” 。可是这种性质 ,真的可以让程序容易“推理 ”吗?如果 Haskell 的函数使用了 monads ,比如“状态 ”(statemonad) ,那么这个函数的“输入 ” ,就几乎永远不会相同 。因为那个状态每次都可能变化 ,所以你实际上还是没法知道那里面是什么!
记住这一点:世界上没有包治百病的神药 。
4. 惰性求值(lazyevaluation)不是好主意
关于惰性求值,我基本同意Robert Harper 的观点 。惰性求值让类型变得混乱 ,让程序的时间和空间复杂度难以分析 ,而且跟并行计算的原则有根本性的矛盾 。而惰性求值的功能,却不是经常有用的 。即使需要 ,在普通的语言里也可以通过 thunk 来实现 。所以 ,惰性求值带来的问题恐怕比它解决的问题还要多 。
很多自称“从 Haskell 衍生 ”的语言 ,很多其实都只是有其形 ,而无其实 。一个例子就是Bluespec ,一种硬件描述语言 。它虽然自称是从 Haskell 演变来的 ,看起来像 Haskell ,但是它却不是惰性的 ,类型系统也很简单 ,所以基本上它已经不是 Haskell。打着 Haskell 的旗号 ,恐怕是想借助 Haskell 的名声来抬高自己 ,或者是因为 Bluespec 的创造者 Lennart Augustsson 最早的时候是 Haskell 的主要发起人之一 。
5. 思想局限
所以综上所述 ,Haskell 自称的“特性 ”几乎被实践一一推翻 。可是 Haskell 程序员往往炫耀自己的“函数式编程 ”水平,其实经常陷入一些很难理解的“设计模式 ” ,无法自拔。鉴于这个原因 ,我停止向大家推荐 Haskell 。
另外需要申明一下的是,我停止推荐 Haskell 并不是因为我想力推 Scheme 。实际上 Scheme 也有自己的问题 ,但是相对来说 ,它更加简单易懂 ,符合学习的需要 。另外 ,以前对 C 和 C++ 的批评也许过于偏激 。最近为了在 LLVM 上做一些事情 ,开始重新理解C++ ,发现它做的好些事情其实是挺不错的 ,甚至超过好些最炫的 ,带有“dependenttype ”的函数式语言 。所以现在我觉得 ,其实世界上的语言并没有什么绝对的标准 。在一段时间认为是错的东西 ,可能却是对的 。所以不用盲目的排斥一些语言 ,把它们都拿来看一下 ,互相对比,才会知道到底什么是好的 。
创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!