简单性、专心编程不受打扰(freedom to focus)、给力(empowerment)、一致性和明确性:Closure编程语言中几乎每一个元素的设计思想都是为了促成这些目标的实现。
学习一门新的编程语言往往需要花费大量的心思和精力,只有程序员认为他能够从他想学的语言中得到相应的回报,这种学习才是值得的。在使用面向对象技术对状态进行管理时,无论是由于面向对象技术内在的因素还是别的偶然因素,都会带来许多不必要的复杂问题,Clojure正是诞生于其创建者Rich Hickey对避免这些问题所做的种种努力。 由于Closure周到的设计方案基于的是在编程语言方面严谨的研究成果,而且在其设计过程中对实用性有着强烈的愿景,所以,Clojure已经茁壮成长为一门重要的编程语言,它在当今编程语言设计领域扮演着一个不容置疑的重要角色。 从一方面讲,Clojure利用了软件事务内存(Software Transactional Memory,简称STM)、agent、在标示(identity)和数值类型(value type)之间划清界线、随心所欲的多态性(arbitrary polymorphism)以及函数编程(functional programming)等诸多手段,提供了一个有助于弄清楚总体状态的环境,特别是在面对并发机制时它更能发挥这方面的作用。从另外一个方面讲,Clojure同Java虚拟机有着密切的关系,从而使得有望使用Clojure的开发者能够避免在利用现有的代码库时再为维护另外一套不同的基础设施付出额外的代价。
在编程语言悠久的编年史中, Clojure 算是一个婴儿; 但它的一些俗语(简单的理解为 "最佳实践" 或者 "习惯用法") 源于拥有50年历史的Lisp语言和15年历史的Java语言。 (在吸收了Lisp 和 Java优秀传统的同时, 在许多方面,Clojure 也象征了一些直接挑战他们的变化。) 另外, 自从它问世以来就建立起来的充满热情的社区,已经发展出属于自己的独一无二的习惯用法集。一种语言的习惯用法有助于将比较复杂的表述 定义成简洁的呈现。 我们肯定会涉及到惯用的 Clojure 代码,但是我们还会更深入地讨论关于语言本身为什么这样实现的原因。
在这篇文章中,我们讨论关于现有编程语言中存在的一些不足,Clojure 正是用来解决这些不足的,在这些领域,它如何弥补了这些不足,以及Clojure体现出的许多设计原则。我们还可以看到一些现有的编程语言对Clojure造成的影响。
其它翻译版本 (1) 加载中让我们慢慢开始吧。
Clojure是一门执着于自己的看法的语言 —— 它并不想包含所有的范型(paradigm),也不想提供一项项的重点特性。相反,它只是以Clojure的方式,提供能够足以解决各种现实问题的所有特性。 为了从Clojure中获得最大的好处,你就应该带着和语言本身相同的愿景来写代码。在逐一讨论Clojure的语言特性时,我们不仅仅会给出每个特性是做什么用的,而且还会讨论为什么会有这样的特性以及特性最好的使用方式是什么。
但在进行这些讨论之前,我们先从一个比较高的层层看看Clojure背后最重要一些理念。图1列出的是Rich Hickey在设计Clojure时心里所想的总体目标以及Clojure所包含的能够支持这些目标得以实现的设计决策。
图1: Clojure的总体目标,给出了Clojure背后的理念中所包含的一些概念以及它们之间的交叉关系。
如图所示,Clojure的总体目标是由若干相互支撑的目标和功能组成的,在下面的几个小节中我们将对它们进行一一讨论。
要给复杂的问题写出简单的答案可不容易。但每个有经验的程序员都曾遇到过将事情搞到没有必要的那种更加复杂程度的情况,为了完成手头的任务,必不可少要处理一些复杂情况,但前面说的这种没有必要的复杂情况与之不同,可以将其称为次生复杂性(incidental complexity)(这个概念的详细情况可以参见"Out of the Tar Pit" )。Clojure致力于在不增加次生复杂性的前提下就可以让你解决涉及大范围的数据需求(data requirement)、多并发线程以及相互独立开发的代码库等等方面的复杂问题。它还提供了一些工具,可以用来减少乍看起来象是具有必不可少的复杂性的问题。最终的特性集看起来并不总是那么简单,特别是在你还不熟悉它们的时候更是如此,但我们认为,你慢慢就会发现Clojure能够帮你剥离多少复杂性。
举个次生复杂性的例子,现代的面向对象的语言趋向于要求每一段可执行的代码都要以类定义的层次、继承和类型定义的形式进行打包。 Clojure通过对纯函数(pure function)的支持摒弃了这一套陈规。纯函数只接受一些参数并且会仅仅基于这些参数产生一个返回值。 大量的Clojure程序都是由这样的函数构成的,而且绝大多数应用程序都可以做成这样式的,也就是说,在试图解决手头的问题时,需要考虑的因素会比较少。
编写代码的过程往往就是一个不断同令人分心的东西做斗争的过程,每当一种语言迫使你不得不考虑语法、操作符的优先级或者继承关系的层次结构时,它都是在添乱。Clojure努力让这一切保持到最简单的程度,从而不会称为你的绊脚石,不会在你象要探索一个基本想法时还需要进行先编译再运行的这种循环动作。它还向你提供了一些用于改造Closure本身的工具,这样你就可以创造出最适合与你的问题域(problem domain)的词汇和语法了 —— Clojure属于表示性(expressive)语言。它非常强大,能够让你以非常简洁的方式完成高度复杂的任务,同时还不会损失其可理解性。
能够实现这种不受打扰专心编程的一个关键在于格守动态系统(dynamic system)的信条。在Clojure程序中定义的几乎所有东西都可以再次进行重新定义,即使是在程序运行时也没有问题:函数、多重方法(multimethod)、类型以及类型的层次结构甚至Java的方法实现都可以进行重新定义。虽然在生产环境中(a production system)让程序一边运行一边进行重定义可能显得有点可怕,但这么做却在编写程序方面为你打开了一个可以实现各种令人惊叹的可能性的世界。用它可以对不熟悉的API进行更多的实验和探索,并为之增添一丝乐趣,而相比之下,这些实验和探索有时会掣肘于更加静态化的语言以及长时间的编译周期。
但是,Clojure可不仅仅是用来寻找乐趣的。其中的乐趣只是Clojure在赋予程序员以前不敢想象的更高的编程效率时,所带来的副产品而已。
有些编程语言之所以会诞生,要么只是为了展示学术界所珍视的某些研究成果,要么就是用来探索某些计算理论的。Clojure可不属于这类语言。Rich Hickey曾在很多场合下说过,Clojure 的价值在于,你用Clojure可以编写出有意思而且也很有用的应用程序。
为了实现该目标,Clojure力求实用 —— 它要成为能够帮助人们完成任务的一个工具。在Clojure的设计过程中,要是需要在一个实用的方案和一个灵巧、花哨或者是基于纯理论的解决方案之间做出权衡选择时,往往实用方案都会胜出。Clojure本可以在程序员和代码库间插入一个无所不包的API,从而将程序员同Java隔离开来,但这么做的话,如果想用第三方Java库就会相当不便。因此,Clojure反其道而行之:它可以直接编译为同普通Java类以及方法完全相同的字节码,中间无需任何封装形式。Clojure中的字符串就是Java字符串;Clojure的函数调用就是Java的方法调用;这一切都是那么简单、直接和实用。
让Clojure直接使用Java虚拟机就是这种实用性方面一个非常明显的例子。JVM在技术方面存在一些不足之处,比如在启动时间、内存使用、缺乏尾递归调用优化技术(tail-call optimization,简称TCO)等等方面。但它仍不失为一种非常可行的平台 —— 它成熟、快速而且已得到广泛的部署。JVM支持大量不同的硬件平台以及操作系统,它拥有数量惊人的代码库以及辅助工具。正是由于Closure这个以实用为上的设计决策使得,所有这一切Clojure都可以直接加以利用。
Closure采用了直接方法调用、proxy、gen-class、gen-interface、reify、definterface、 deftype以及defrecord等等手段,都是致力于提供大量的实现互操作性的选项,其实都是为了帮你完成手头的任务。虽然实用性对Clojure来说非常重要,但是许多其它的语言也很实用。下文你将通过查看Clojure是如何避免添乱的,从而领会Clojure是如何真正成为一门鹤立鸡群的语言的。
When beetles battle beetles in a puddle paddle battle and the beetle battle puddle is a puddle in a bottle they call this a tweetle beetle bottle puddle paddle battle muddle. — Dr. Seuss (译者注:这是一段英文绕口令,大致意思是:甲壳虫和甲壳虫在一个水坑里噼里啪啦打了起来,而且这个水坑还是个瓶子里的水坑,所以他们就把这种装了滋滋乱叫的甲壳虫的瓶子叫做噼里啪啦乱作一团的水坑瓶。。。)
请看下面这段可以说是非常简单的一段类似Python的代码:
x=[5] process(x) x[0]=x[0]+1这段代码执行结束后,x的值是多少?如果你process并不会修改x的值的话,就应该是[6],对吧?可是,你怎么能做出这样的假设呢?在不准确乱叫process做了什么以及它还调用了哪些函数的情况下,你根本就无法确定x的值到底是多少。
即使你可以确信process不会改变x的值,加上多线程后你要考虑的因素就又多了一重。要是另外一个线程在第一行和第三行代码之间对x的值进行了改变会出现什么情况?让情况变得更加糟糕的还有,要是在第三行的赋值操作过程中有线程对x的值进行设定的话,你能确保你所在的平台能够保证修改x的操作的原子性吗?要么x的值最终会是多个写入操作造成了乱数据?为了搞清除所有情况,我们可以不断继续这种思维练习,但最终结果毫无二致 —— 最后你根本就搞不清楚所有情况,最终结果却恰恰相反:乱作一团。
Clojure致力于保持代码的明确性,它提供了可以用来避免多种混乱情况的工具。对于上一段所述的问题,Clojure提供了不可变的局部变量(immutable local)以及持久性的集合数据类型(persistent collection),这二者可以一劳永逸地排除绝大多数由单线程和多线程所引起各种问题。
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。 2KB翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。2KB项目(www.2kb.com,源码交易平台),提供担保交易、源码交易、虚拟商品、在家创业、在线创业、任务交易、网站设计、软件设计、网络兼职、站长交易、域名交易、链接买卖、网站交易、广告买卖、站长培训、建站美工等服务