由Amit Rahore及Francis Avila撰写,Manning出版的 《Clojure in Action》第二版 以本质性的、通透的、结构组织良好的方式对Clojure 1.6进行了介绍。本书探索了该语言的核心部分内容,并为读者介绍了Clojure的编程与习惯。InfoQ与本书的作者之一Francis Avila进行了一次访谈。
本书的第一版涵盖了Clojure 1.2版本的特性,而在第二版则针对Clojure 1.6版本进行了大幅更新。原先的几个章节中包含了一些外部库的介绍,而这些库有些已经被取代,或已经过时。因此这些部分在第二版中已经被删除,取而代之的是对Java与Clojure互操作性的关注。这表现了Clojure近几年间已经得到了更大范围的程序员社区,乃至非Java程序员的接受。
《Clojure in Action》旨在完整地涵盖Clojure的各方面细节,但也没有忽略它所依赖的JVM生态系统。整本书的编写方式可以看出一点敏捷的味道,主要体现在书中基本找不到没有意义的重复信息。本书以一种有趣的方式为读者展现了示例代码,许多代码都是直接取自于Clojure的标准库,例如对Clojure宏系统的讨论。因此这些代码非常接近于真实的Clojure代码。另一方面,作者在许多示例中尝试创建了一些抽象概念,这与Clojure中现有的抽象可谓相辅相成,例如对多态与协议的讨论。
本书共包括11个章节,完整地涵盖了Clojure语言的方方面面。首先介绍的是它的构建块,例如数据结构、函数、条件、命名空间等等,随后逐步开始介绍更高级的主题,例如multimethod、宏与协议等等。本书的最后两个章节介绍了两个独特而又高度相关的主题:一是如何在测试驱动开发实践中应用Clojure,其中详细地论述了单元测试、桩(stubbing)与模拟(mocking)技术。二是如何编写高级的宏与领域特定语言(DSL)。
InfoQ与本书的作者之一Francis Avila进行了一次访谈,以进一步了解本书出版的情况、Clojure的优点以及未来的发展。
Francis Avila:编写本书最基本的动力是帮助来自于更主流编程背景(Java、C#、Python、Ruby和JavaScript等等)的程序员学习Clojure以及它的核心哲理:对于状态以及时间的管理、默认的不可变性、尽可能使用纯粹的函数式数据转换、以及将代码作为数据进行操作。在编写本书的第一版时,这些思想还没有成为主流,而Amit Rathore试图让人们了解Clojure的优势的呼声并没有引起人们的注意。对于Clojure有兴趣的人绝大部分都具有Java的背景,他们需要支持大型的Java生态系统,希望能够以一种更好的工具来管理这些复杂性。
第二版最主要的目的是更新书中的内容,以包含Clojure 1.6中的变更(第一版所针对的是Clojure 1.2)。但这些年以来,编程领域已经发生了很大的变化:不可变性与函数式编程不再是离经叛道的思想了(尤其是对于动态类型的语言来说),并且越来越多来自Java世界之外的程序员开始对Clojure感到好奇。因此,这本书的必要性就在于为更大范围内的程序员介绍Clojure这门语言。令人啼笑皆非的是,这反而意味着本书在一定程度上变成了Java与JVM的辩护者,而这对于来自动态语言背景的程序员来说是一个不熟悉(并且抱有某种怀疑的态度)的概念。我知道这一点,是因为我自己就是这些程序员中的一员!
Francis:我的知识背景与计算机科学毫无关联,而是人文学科:包括哲学、神学、古希腊语以及古拉丁语。但我从小就是一位极客,在大学里自学了Linux与Python。在毕业之后,我成为了一位web开发者,靠着PHP与其他一知半解的知识编写代码。我是从阅读了由Bruce Tate所撰写的《七周七语言》一书才认识Clojure(本书介绍的七种语言之一)的。我对Rich Hickey提出的不可变性、状态以及复杂度等思想很感兴趣。随着我对Clojure的学习越深入,我越感觉到它证实了我在管理大型软件项目时被掩盖的一些直觉(在PHP项目中,如果你无法管理好它的复杂性,就会反过来被它牵着鼻子走!)。
之后我总算在我当时所在之处找到了一家招聘Clojure开发者的公司,我很快应聘了这个职位,并于2012年中段加入了Breeze。Breeze是一家位于路易斯安那州的小型电子健康记录公司,整个公司基本上只用Clojure和ClojureScript进行开发。
Francis:我能够建议的最重要一点就是在阅读过程中始终打开Clojure REPL,并且在思考或遇到问题时进行一些尝试。读者可以挑战一些极端的情况,并观察一下发生了什么。如果你希望节省一点打字的时间,可以随意下载本书中的代码(为各章节设置了对应的命名空间以组织代码)。如果你能够找到一个集成了REPL的编辑器或IDE,例如Cursive(IntelliJ IDEA)、LightTable或Proto REPL(ATOM),那么整个过程会更为顺利。通过使用集成在编辑器中的REPL,你可以在文件或缓冲中进行编辑,在编写代码的同时自动进行重新评估、重新定义与测试你的代码,而不是直接将代码复制粘帖至命令行中。
如果你遵循这些建议,那么在读完本书后,你就能够扎实地掌握编写Clojure代码的机制、如何从Clojure的角度处理问题、以及于何时何地应使用Clojure抽象工具中的哪一部分(例如在何时,以及如何使用一等函数、multimethod、协议、记录以及宏等等)。
不过,本书并不打算帮助你熟悉当前Clojure库的生态系统以及系统级别的模式。本书的第一版对于这一点投入了许多精力,但这方面的信息实在变化得太快。自打那时以来,大部分库都发生了极大的改变,或是被更好的技术完全取代了。因此,我们并不打算更新这些部分,而是缩减了这部分内容,更专注于如何开发Clojure的核心能力,将库的依赖方面的内容降至最低。
Francis:通过Clojure提高生产力的最重要因素是它默认提供了丰富的不可变数据结构,让开发者不必再担忧“如果其他代码改动了这里怎么办”,或是“哪段代码改动了这里?”,这就大大地减少了bug的数量和为此浪费的脑细胞了。“专注小块功能”的编程方式比传统方式简单了许多,开发者可以专注于小块功能的开发,随后再将这些功能组合在一起。
让不可变性成为整个Clojure生态系统中默认的行为,对于其他的Clojure特性也带来了很大的影响,如若不然,那些特性本身就会显得暗淡许多。举例来说,Clojure中提供的核心函数式编程结构(map、reduce等等)让创建简单的、以数据为中心的转换管道变得更方便,但其他许多语言也提供了相同的功能(包括Java!)。不过,一旦这种特性与不可变数据结构结合在一起,就能够发挥出惊人的威力,因为你可以随意地重新排列与检查管道中的数据,而不必担心造成无意中的mutation(变动)。
Francis:宏是Clojure的value proposition特性的一个关键组成部分(同样也适用于LISP),有效地利用宏是一项关键的技能。与其他语言中的常见技术相比,使用宏编写DSL将简便许多。宏的作用是控制在合法的Clojure程序中所嵌入的form的评估(在评估之前,代码即数据),否则的话,你将不得不从头开始完成各种任务,例如编写自己的语法、对应的记法分析器或解析器、AST以及运行时。你可以利用Clojure运行时的完整功能,你所要做的主要工作就是添加自己的语法,并扩展至可执行的Clojure程序中。
此外,除了编写DSL,宏还有更广泛的应用场景。如果你想要获取某个计算结果,而不是让代码去执行该计算(即“预编译”的值)、用内联代码取代函数调用、或是控制是否要评估某个代码块,以及控制评估的顺序(例如在某个控制流中),你就必须编写宏,因此了解他们的工作方式非常重要。
Francis:“偶然的复杂性”是我为那些由我们程序员本身造成的问题所取的名字。“复杂性”对应着“简单性”:简单的思想指的是,为了保持它的性质,不可从中去除任何一部分内容。而复杂的思想意味着仍然有部分内容是可以抛弃的。“偶然的”对应着“本质的”:复杂性并不属于要解决的问题的一部分(本质的),而属于解决该问题的方式的一部分(偶然的)。
Clojure通过所提供的更简单的抽象方法避免了这种偶然的复杂性:这种“简单”并不是指“更易于使用”,而是指“没有任何多余的东西可以丢弃”。通过几个例子的对照可以便于我们理解这一点。
在大多数编程语言中,集合(例如一个引用数组)都是可变的:这表示它目前包含了某些成员,而之后可以包含不同的成员。这些集合是“复杂的”,因为他们的概念包括值(我包含了哪些成员),以及标识(我在内存中的地址是什么)和时间(我目前包含了哪些内容)。如果你试图共享这个集合,则使用者在改变其中的内容之前,必须小心地生成一个拷贝(因为它的值与标识是不可分割的)。如果你想获得该集合的一个快照,则必须在程序运行时拷贝整个集合的状态。
而在Clojure中,这三者将由不同的抽象概念所服务。集合(我一直在谈论的那种不可变的数据结构)仅仅是值的表现,因为他们不能够进行变更,并且与其他集合的等价性与他们在内存中是否具有相同的地址以及他们是否具有相同的底层实现无关。由于集合是不可变的,因此也是非时变(a-temporal)的:你可以“拥有”对整个程序在某一特定时间整个状态的引用,而这种状态是不可能发生变化的。它的值取决于你在哪个box中所输入的值。这些值在Clojure中有4种表现形式,每种形式都表现了随着时间的推移对变更进行管理的一种特定方式,例如异步与同步、协调与独立。这些值所在的box只包括标识,并且只对你所需的特定变更(例如时间)进行管理。
当你具备了一定Clojure的开发经验后,你就会察觉到多数问题都可以由值和纯函数解决,纯函数是指输入某些值,并生成另一些值的函数。只有少数场合下,你才需要mutation(它必定要结合时间与标识)所带来的复杂性,在这种场合中,你需要使用Clojure中的某种引用类型(例如包含值的box)。但是,在大多数编程语言中,无论你是否需要,你总要为时不时发生的mutation付出代价。这将成为一种偶然的复杂性,它与你处理问题的解决方案无关,而你仍然要对此进行管理。
如果你希望能够快速地了解这些核心思想(状态、值、时间、标识和行为),以及他们在Clojure与面向对象编程中的含义的对照,我建议你阅读一下这篇博客文章:“ The unofficial guide to Rich Hickey’s Brain ”。
Francis:最难学习的技能其实是一种“软”技能:即将你的程序想象为数据的转换,而不是操作或行为。训练这一技能的一种方式是经常性地观察你的函数并进行重构:
Francis:从企业的角度来说,只需更少的代码与更少的开发者就能够完成相同的工作,而且更少的代码库将更易于改动。只需很少的精力就能够达到优秀的应用性能(它并非绝对最快的语言,但非常接近于Java的性能,并且比起Python或Ruby快了一个数量级)。总的来说,Clojure在以数据为中心(而不是以计算为中心),并且需求频繁改动的问题领域中能够发挥最大的功效。不过,寻找Clojure开发者比起Java等语言的开发者更困难,但你也无需大量的Clojure开发者。
不过,在许多问题领域中,代码的安全性或性能是至关重要的因素,并且需求相对比较确定。你可以选择某些结构式类型(Prismatic的Schema)或渐进代数式类型(Typed Clojure)的库,但这些库是可选的,或者还不够成熟。Clojure也不适用于一些受限的环境,例如手机或平板电脑(有一些相关的替代方案可以用于小型客户端,例如ClojureScript或Skummet)。
Francis:许多应用如今都已转变为基于web的部署方式,他们需要进行横向扩展而不是纵向扩展,而不可变性从微观角度(线程级别的并发性)与宏观角度(集群级别的并发性)两方面大大地简化了横向扩展的过程。我认为这正是Clojure在商业项目上取得成功的一大因素。
另一个因素是Clojure托管于JVM环境中。一方面,人们在Java方面已经获得了丰富的经验,拥有大量的专家及软件,但他们又受挫于Java企业生态系统的复杂性。Clojure让你能够逐步地迈向简单性的目标,而又无需完全抛弃过往的经验:它比Java更简单,表达能力也更强,但它也能够与Java共存。因此,对于那些不愿意完全受限于Java,但又无法接受以截然不同的技术取代Java的风险的组织来说,Clojure是一个良好的替代方案。
另一方面,来自动态语言背景的人往往是由于Java的静态类型以及复杂性方面的因素放弃了它,而Clojure对他们来说是一种可接受的语言,它同样是动态类型的语言,并且对于函数提供了一等支持,因此运行速度比本身的语言要快得多。当前最流行的动态语言实现都有着性能或并发性方面的限制(例如全局解释器的锁,又或者完全不支持线程!),而JVM并没有这方面的问题。Clojure为这些开发者提供了一条出路,让他们能够利用JVM的强大特性,而又无需过多地触及Java。
Francis:我认为Clojure开发者仍然受困于三个方面的问题,这或许将成为Clojure未来发展的动力。
首先,Clojure运行时非常庞大,造成了启动的缓慢。对于那些在服务器环境中长时间运行的应用来说,这一点不成问题,但越来越多的人希望在运行时间较短的程序(例如命令行工具)或在一些资源受限的机器(例如Android手机)中使用Clojure。Clojure的极端动态特性此时就成了一个不利因素,因为它会降低应用的速度。Clojure 1.8增加了一个可选的编译设置,名为“直接链接”,当某个var没有经过重定义时,它可消除var的间接特性。从而允许Clojure生成更小的Java类以及更多的静态调用。虽然它能够带来一些帮助,但仅有这一点还不够。此外,还有一些点子或许能够在未来的Clojure版本中出现,例如通过tree-shaking(即删除无作用的代码)减小代码的体积,以及通过命名空间延迟加载的方式加快启动的速度。
其次,Clojure的实现将出现在越来越多的主机上。ClojureCLR(.NET平台上的Clojure)出现的时间几乎与Clojure本身一样长,而ClojureScript在过去一年间的流行度得到了爆发式的增长。Clojure曾经有一种以主机为中心的哲学,但越来越多的开发者希望编写的Clojure代码至少能够实现在Clojure与ClojureScript之间的相互移植(实际上,有许多Clojure库已经能够在不进行改动的情况下运行在这两个环境中了)。Clojure 1.7引入了一个名为“reader conditionals”的特性,允许在Clojure实现中的同一个.cljc文件中对代码段进行条件式评估。我希望开发者能够投入更多的精力,将不同版本的Clojure之间的平台区别保持在最低。比方说,Clojure 1.8引入了额外的string函数,使开发者不必再调用某些Java方法,并提高了Clojure与ClojureScript代码之间的可移植性。
最后,人们对于代数类型(Algebraic Typing),尤其是渐进类型(Gradual Typing)产生了越来越多的兴趣。程序员希望编译器能够对类型及不变量进行检验,以减少他们必须编写的测试数量,同时又不希望被迫在整个程序中明确地定义类型。如我所说,在这方面已经展开了一些工作。在Clojure生态系统中已经出现了Prismatic Schema、Herbert和大量其他的运行时结构类型化库,而Typed Clojure项目还提供了可选的编译期渐进类型化的功能。我认为这些项目(尤其是Typed Clojure)能够进一步成熟起来,并得到更多的应用。不过,我并不认为在Clojure核心功能中会为此进行任何改动。
读者可以选择购买本书的电子版或印刷版,本书的风格在可免费下载的两章中已得到了充分的展现,这两章分别是: Introducing Clojure ,以及第8章, More on Functional Programming 。
Francis Avila 过去8年间一直在从事web方面的开发工作。在过去4年内,他在Breeze编写了大量专业的Clojure与ClojureScript代码,包括前端与后端代码。Breeze是一家医学方面的小型软件公司,Clojure是该公司唯一的开发语言。Francis与他的妻子和两岁的女儿居住在路易斯安那州的拉斐特。
查看英文原文: Clojure in Action, Second Edition, Review and Authors Q&A