学生们经常要求推荐他们下一步应该学习什么语言。如果你正在找一份工业界的工作,我的回答是学习目前最热门的东西:C++、Java和C#--可能还有Python、Ruby、PHP和Perl。
另一方面,如果你对启蒙、学术研究或初创企业感兴趣,那么你选择下一门语言的标准不是就业能力,而是表达能力。在学术研究和创业中,你需要提高你作为程序员的效率,因为你(可能)不会使用根深蒂固的代码库,所以你可以自由地使用任何最适合手头任务的语言。
在这里,您将找到四种值得学习的好语言的描述--Haskell、Scala、ML和Scheme--每种语言都列出了我最喜欢的特性,并指出了在哪里可以学到更多。
当然,这份简短的名单绝不是详尽的。有许多不同寻常的语言擅长于利基语言。仅举几个例子,还有用于系统编程的D;用于并发的Erlang或Clojure;以及用于约束编程的Datalog。然后还有像Smalltalk这样的语言--很久以前从主流计算中分支出来的另类但功能齐全的领域。
我鼓励我的学生永远不要停止学习利基语言。它们拓展了你的思维方式,拓展了你快速解决的问题种类,拓展了你对计算意义的理解。
Haskell作为编写编译器、解释器或静态分析器的语言表现出色。我不做很多人工智能、自然语言处理或机器学习研究,但如果我做了,哈斯克尔也会是我在那里的第一选择。(该计划将是第二个强有力的计划。)。Haskell是唯一广泛使用的纯惰性函数式编程语言。
与Standard ML和OCaml一样,Haskell使用了Hindley-Milner风格的类型推断的扩展,这意味着程序员不必写下(大多数)类型,因为编译器可以推断它们。根据我的经验,通过Hindley-Milner类型的系统很难获得bug。事实上,有经验的程序员能够熟练地将正确性约束直接编码到Haskell类型的系统中。在第一次用Haskell(或ML)编程后,人们常说的一句话是,一旦程序编译完毕,几乎可以肯定它是正确的。
作为一种纯语言,其副作用(变量或数据结构和I/O的突变)在语言本身中是被禁止的。这迫使该语言的设计者认真考虑如何提供这样的功能。他们的答案是Monads,使人们能够在安全约束的框架内执行副作用和I/O。当然,Haskell允许用户定义他们自己的Monad,现在程序员可以访问Monad来进行延续、传感器、异常、逻辑编程等等。
除了纯洁之外,哈斯克尔还很懒。也就是说,Haskell中的表达式直到(并且除非)其结果是向前计算所必需的,否则不会求值。一些人争辩说,承诺的懒惰带来的效率收益并没有实现,但这并不是我关心的问题。我欣赏懒惰,因为它增加了表现力。在Haskell中,描述无限范围的数据结构是微不足道的。在其他语言允许相互递归函数的情况下,Haskell允许相互递归的值。
更务实的是,我发现懒惰在编码选项类型时很有用,在这种情况下,利用空格应该总是会破坏程序。在Haskell中,您可以避免创建选项类型,而是使用ERROR生成空值。由于懒惰,Haskell中的每种类型都自动具有两个附加值:不终止和错误。使用得当,这消除了许多繁琐的模式匹配。
我最喜欢的Haskell特性是类型类。Haskell的类型系统允许编译器根据其类型上下文推断要运行的正确代码,即使该类型上下文也被推断出来。让我兴奋的类型类的例子是有界格。有界格是具有最小元素(BOT)、最大元素(TOP)、偏序小于关系(<;:)、联接运算(Join)和相交运算(Meet)的数学结构。
在Haskell中,可以将有界格定义为类型类:class Lattice a where top::a bot::A(<;:)::A->;a->;Bool Join::A->;a-&>;a->;a。
这就是说,如果类型a是一个格子,那么a就支持预期的操作。
我真正喜欢Haskell的地方是,它允许程序员定义类的条件实例;例如:Instance(Ord k,Lattice a)=>;Lattice(Map K A),其中bot=Map.Empty top=error$";无法表示。";f<;:g=Map.isSubmapOfBy(<;:)f g f`joining`g=Map.unionWith Join f g f`Meet`g=Map.inter.。
这条规则说,如果类型k是序的实例(类Ord),类型a是格的实例,那么从k到a的映射也是格的实例。
作为另一个示例,您可以很容易地将两个晶格的笛卡尔乘积转换为晶格:Instance(Lattice a,Lattice b)=>;Lattice(a,b),其中bot=(bot,bot)top=(top,top)(a1,b1)<;:(a2,b2)=(a1<;:a2)||(a1=a2&;&;b1<;:b2)(a1,b1)`加入`(a2,b2)=(a1`加入`a2,b1`加入`b2)(a1,b1)`相遇`(a2,b2)=(a1`相遇`a2,b1`相遇`b2)
它很容易将网格操作、关系和元素自然地提升到几乎任何数据结构。最终结果是,如果您在代码中的任何位置使用表达式bot或关系<;:,Haskell就可以在编译时根据表达式的类型(它也可以推断)推断出它们的适当含义。
ML语言有函数器来扮演类型类的角色,但是它们缺乏对Haskell类型类的即席多态性支持。在MLS和Haskell中花费了相当多的时间进行编程,推理对表现力的实际影响怎么强调都不为过。
真实世界的哈斯克尔。正如书名所暗示的,本书关注的是将Haskell用于实际应用程序(例如,Web编程),而不仅仅是编译器、解释器和程序分析器。
Scala是Java的一个坚固、富有表现力、绝对优越的替代品。Scala是我用于编写Web服务器或IRC客户端等任务的编程语言。与OCaml不同的是,OCaml是一种移植了面向对象系统的函数式语言,而Scala感觉更像是一个真正的混合体。也就是说,面向对象的程序员应该能够立即开始使用Scala,只按照他们选择的方式选择功能部分。
我是从马丁·奥德斯基(Martin Odersky)在2006年POPL的特邀演讲中了解到Scala的。当时,我认为函数式编程严格优于面向对象编程,所以我不认为需要一种融合了函数式编程和面向对象编程的语言。(这可能是因为我当时写的只有编译器、解释器和静态分析器。)。
直到我从头开始编写了一个并发httpd来支持针对yaplet的长期轮询的Ajax,我才意识到对Scala的需求。为了获得多核支持,我用Java编写了第一个版本。我不认为Java有那么糟糕,我可以享受做得很好的面向对象编程。然而,作为一名函数式程序员,缺少对函数式编程特性(如高阶函数)的简洁支持让我很恼火。所以,我给了斯卡拉一个机会。
Scala在JVM上运行,因此我可以逐步将现有项目移植到Scala中。这还意味着,Scala除了拥有相当大的库之外,还可以访问整个Java库。这意味着您可以在Scala中完成真正的工作。
当我开始使用Scala时,函数世界和面向对象世界是如此紧密地混合在一起,给我留下了深刻的印象。具体地说,Scala有一个强大的case类/模式匹配系统,解决了我使用Standard ML、OCaml和Haskell时挥之不去的烦恼:程序员可以决定对象的哪些字段应该匹配(而不是强制所有字段都匹配),并且允许使用可变参数。事实上,Scala甚至允许程序员定义的模式。
我编写了很多在抽象语法节点上操作的函数,所以只在语法子节点上匹配,而忽略注释或源位置的字段是很好的。
CASE类系统允许跨多个文件或同一文件的多个部分拆分代数数据类型的定义。Scala还通过称为特征的类结构支持定义良好的多重继承。而且,Scala允许操作符重载;甚至函数应用程序和集合更新也可以重载。如果使用得当,这往往会使我的Scala程序更加直观和简洁。
事实证明,与类型类在Haskell中节省代码的方式相同,有一个特性可以节省大量代码,那就是隐含。您可以将隐含想象为类型检查器的错误恢复阶段的API。简而言之,当类型检查器需要X但得到Y时,它将检查在作用域中是否有标记为隐式的函数将Y转换为X;如果找到,它会自动应用隐式函数来修复类型错误。
含义使您看起来像是在有限范围内扩展类型的功能。例如,假设您想要将";一个egrieHTML()方法添加到字符串类型中。您不能修改String的定义,但可以通过隐式修改,以便当myString.scape eHTML()的类型检查失败时,它将在作用域中查找隐式函数,该函数可以将String对象转换为支持audeHTML()方法的类型。
IMPLICE还允许在Scala中使用更干净的特定于域的嵌入式语言(DSEL),因为它们允许您将Scala文字(如3或#34;While)透明地映射到dsel中的文字。
Martin Odersky(Scala的创建者)、Lex Spoon和Bill Venners在Scala中的编程既是一个很好的介绍,也是一个很好的参考。
ML家族是语言设计领域的一个甜蜜点:严格的、副作用大的和Hindley-Milner类型推断的。这使得这些语言适用于需要高性能和更强的正确性保证的实际项目。ML家族得到了航空工程师(因为它对无bug代码的支持)和金融行业的程序员(出于同样的原因)的支持。标准ML是我学得很好的第一种函数式语言,所以我仍然记得被它的表现力所震惊。
今天,OCaml似乎是最受欢迎的学习ML,但至少有一个令人信服的论据支持SML';MLton。MLton确实阐述了函数式语言提供最好的优化机会这一论点。作为一个全程序优化编译器,我还没有看到另一个编译器的性能能与之匹敌。我曾经为MLton创建了OpenGL绑定来玩弄3D图形,结果得到的程序比我用作参考的基于C++的模型运行得更快,只用了10%的代码。
SML中的函数器系统虽然比Haskell的类型类系统更冗长,但更灵活。一旦您在Haskell中为种类/类型k实例化了一个类型类T,您就不能为该种类/类型再次实例化该类型类。使用函数器,每个实例都有自己的名称,因此您可以拥有同一类型的给定函数器的多个实例。我很少需要这样的表现力,但在那些我需要的情况下,这是很好的。
ML族谱上的另一个现代分支OCaml是很好了解的,因为有一个很大的社区投资于它,这意味着有很多可用的库。OCaml工具链也很丰富,开发人员可以使用解释器、优化编译器和字节码编译器。
因为ML语言比所有主流语言更有表现力,但是它们仍然允许产生副作用,所以它们在学习Haskell的过程中做了一个很好的停顿。在Haskell中,还不精通函数式程序设计的程序员可能会发现他们在角落里重复编写代码,在那里他们无法访问他们需要的Monad。MLS保持对不完整设计的副作用进行修补,这可以防止项目突然出现不可预料的重构或中止决定点。(工业和信息化部电子科学技术情报研究所陈冯富珍译为“逃生舱门”,即“逃生舱门”),以防止项目突然出现意外的重构或中止决策点。语言的一个有用的衡量标准是它对软件系统的糟糕或不完整设计的容忍度,因为设计不可避免地会随着程序的发展而变化。在这方面,MLS仍然比哈斯克尔占上风。
面向正在工作的程序员的ML可作为SML的很好的入门和参考文本。这是我学到的一本书,它很好地介绍了ML语言家族(包括Haskell)的编程思想过程。它还介绍了如何实现SML库没有提供的重要功能数据结构(树和映射)。
Schema是一种具有纯粹核心(λ演算和列表理论)和最大化表达自由的设计任务的语言。它是非类型化的,这使得它非常适合基于网络的编程和快速原型制作。考虑到它的Lisp传统,Scheme是人工智能的天然选择。
由于支持任意精度的数字,Scheme也是我实现密码算法的首选。[有关示例,请参阅我对RSA的简短实现,以及Scheme中的Fermat和Solovay-Strassen素数测试。]。
到目前为止,使用Scheme最有说服力的理由是它的宏观系统。Scheme可用的所有宏系统,包括标准语法规则和语法大小写系统,都是图灵等价的。
因此,程序员可以重新配置方案,以减少语言和手头任务之间的阻抗失配。与对一流延续的支持相结合,甚至可以嵌入替代编程范例(如逻辑编程)。
例如,在代码中:(let((x(Amb345))(y(Amb678)(assert(=(+xy)12))(Display X)(Display Y))。
可以编写一个amb宏,该宏选择正确的参数使后续的ASSERT语句为真。(此程序先打印4,然后打印8。)。
在Scheme中,在计算的任何时间点,程序都可以捕获当前延续作为过程:调用此过程会将程序返回到捕获延续时存在的求值上下文。使用延续的编程感觉就像在时间中来回旅行,在平行的宇宙之间转换。
归根结底,Scheme是如此的小巧和可扩展,所以关于它没有太多可说的,除了Scheme允许程序员从语言中提取程序员愿意放入其中的任何内容。
球拍(前身为PLT Scheme)是一个包含电池的Scheme系统,包括一个经过战斗测试的IDE、一个编译器和一个解释器。更重要的是,racket库非常庞大:它有一个将类型系统添加到语言中的模块;它有一个添加模式匹配的模块;它有一个用于OpenGL编程的模块;它还有一个用于基于延续的web服务器的模块。在球拍中,几乎所有的东西都已经有了一个模块。
我在球拍上看到的最好的一本书--球拍领域--通过游戏编程介绍了这种语言的特点: