Clojure编程实战(原书第2版)(txt+pdf+epub+mobi电子书下载)


发布时间:2020-10-01 17:55:01

点击下载

作者:(美)阿米特·拉索尔(Amit Rathore),(美)弗朗西斯·阿维拉(Francis Avila)

出版社:机械工业出版社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

Clojure编程实战(原书第2版)

Clojure编程实战(原书第2版)试读:

译者序

自编程语言出现以来,更好地管理复杂的抽象、清晰且简短的代码以及编程语言本身的可扩展性一直是无数程序员追求的目标,这催生了以静态类型、面向对象编程方法为基础的Java等企业级编程语言,以及以快速Web开发为目的的JavaScript、Ruby、Python等动态类型脚本语言。

但不管是上述的哪一种语言,在新的问题领域不断出现之时,它们都带来了越来越多的附带复杂性,那么如何控制这种复杂性,同时利用历史遗留下来的巨大生态系统及已有代码呢?Clojure就是这样的利器。它具备Lisp类语言的函数式编程风格,通过高阶函数、不可变数据对象等特性,生成更加清晰、一致且易于理解的代码,而且极好地解决了多线程条件下的编程难题,达到了其他语言难以企及的并发程度。Clojure语言核心短小紧凑、语法简单明了,许多功能是通过其强大的宏系统实现的,这又使它具备了自扩展能力,可以轻松地增加语言特性甚至为非编程人员提供强大的领域特定语言。最难得的是,Clojure植根于JVM,并且提供了丰富的Java互操作功能,不仅可以轻松地共享Java语言长年积累下来的强大程序库和生态系统,利用原有代码,还可以让Java开发团队享受到新型语言的便利,可谓“鱼与熊掌兼得”。

本书的两位作者来自不同的开发领域,他们将企业编程和Web应用开发的知识熔于一炉,用既贴近现实应用又便于理解的实例,阐述了编程语言中这一后起之秀的方方面面。在不知不觉之中,读者可以体会到函数式编程的威力,熟悉原本令人望而生畏的独特语法,最终沉浸于新技术带来的快乐之中。详尽的解说、丰富的实例,也使本书成为Clojure语言的必备入门书籍。

本书的翻译工作主要由姚军完成,徐锋、刘建林、陈志勇、宁懿、白龙、陈美娜、谢志雄、方翊、林耀成、陈霞等人也为翻译工作做出了贡献,在此衷心感谢华章公司的编辑王春华老师和其他有关人员为本书所提的宝贵意见。姚军2018年7月

第1版赞誉

“一本容易理解的书,也是Clojure的快速入门途径。”——Craig Smith,Suncorp“广泛而全面地概述了这种激动人心的新语言的当前状态。”——Tim Moore,Atlassian“对构建实际应用程序所需的知识做了实用、全面的介绍。”——Stuart Caborn,BNP Paribas“我喜欢书中加入的测试和Web主题!”——Chris Bailey,HotelTonight“对Clojure及其在JVM语言系列中的独特地位有着深刻认识,是每位试图掌握Clojure的读者的必备读物。”——Jason Rogers,MSCI Inc.“不仅学习Clojure——还学习如何用它来构建各种程序。”——Baishampayan Ghose(BG),Qotd,Inc.“用Java解释了函数式编程。”——Doug Warren,Java Web Services“这本书告诉你,结合Java程序库和一种务实的函数式语言能实现怎样的目标。”——Federico Tomassetti,Politecnico di Torino“非常容易理解的文字,出色的Clojure和Lisp入门书籍。”——Kevin Butler,HandyApp,LLC“介绍Clojure的各种特性,说明如何组合它们以实现多种工程解决方案。每种解决方案都极其简洁。强烈推荐这本书。”——A.B.,Amazon评论

第2版序言

许多新接触Clojure的人都来自企业软件领域,包括本书的主要作者阿米特·拉索尔。在他们的世界里,刻板的静态类型、面向对象语言与由工具、框架和程序库组成的庞大生态系统联系在一起,这些工具、框架和程序库的设计主旨是降低组件和不断变化的业务需求之间的耦合度。这是包含依赖注入、Servlet容器、XML配置和代码生成的Java及C#世界。因为Clojure运行于Java之上,所以它成为试图摆脱这一领域复杂性同时又不想完全放弃熟悉且优秀工具的人的不二之选。对于Clojure,企业软件开发人员害怕和不熟悉的特性是动态类型和一阶函数,但是Clojure吸引人的地方在于摆脱附带复杂性和静态类型,同时仍然可以在必要时使用旧代码。

我来自Web开发的“狂野西部”,那是PHP、JavaScript、Python和Ruby等动态类型编程语言的疯狂世界。这些语言中,有些在最初设计时很少(甚至没有)考虑过在大项目上的实用性,并且为了适应大项目而匆忙地发展出了新功能和变通方法。许多使用者(包括我)都没有经过计算机科学训练,职业生涯可能始于摆弄HTML,为日常工作提供一个网站。他们的编程知识和所使用的语言一样,都是随着网站的成长而匆忙获得的。和企业软件领域不同,动态类型、自动类型强制和后期绑定是常规做法,第一类函数很常见,面向对象也不是基本前提。在这个领域里仍然有由框架和程序库组成的大型生态系统,但是它们不像企业软件开发中那样遵守规范和面向配置。对于Web开发人员来说,Clojure最可怕的是其背后潜伏的企业软件“幽灵”——(简言之)Java。对于企业开发人员来说,Clojure的Java传承是一个特性,而这对于Web开发人员则是一个bug。

如果你来自Web开发者的世界,那么我可以告诉你:不用害怕Java。许多企业软件的复杂性是体现在编译时的:静态类型、冗长的代码和许多XML配置。在流行的Web开发语言中,复杂性体现在运行时:弱类型和极端的动态性及易变性使程序难以推导。我正是在寻找这种附带复杂性的更好解决方案时发现了Clojure,我对Java也持怀疑态度。我听说过Java EE的各种传说,看到过庞大的类文件和工厂接口。我感到疑惑:Clojure是在Java这种死板、脆弱和复杂的软件栈基础上构建的,那么它怎么能够更好地控制软件复杂性?该如何平衡所有括号?

Clojure处于混乱的Web开发领域和过于规格化的企业软件领域之间,前者的代码库难以安全地更改,后者的代码库则冗长且难以理解。Clojure在我的程序上施加的限制比编写PHP时更严格,但是这种纪律性没有任何负面影响:你的代码仍然和往常一样简洁(也许还更简洁);你可以轻松且毫无痛苦地利用Java生态系统的许多优势,例如健全的包管理和基于JAR的部署;由于JVM,你的应用程序还可能运行得更快!

甚至在我成为专业的Clojure编码者之前,它就已经给我带来了好处。深入理解Clojure的简洁性和不可变性哲学,帮助我认识到在其他语言编程中遭遇的复杂性的根源,从而更好地控制复杂性。我现在以编写Clojure(和ClojureScript)为生,诚然,我的软件中仍然有许多附带复杂性,但是发现和控制它们变得更轻松了,我所编写的软件是使用PHP或者Python时做梦也想不到的。

本书的第1版帮助我走上Clojure的道路,那正是我现在的道路。我很荣幸能参与第2版的写作,希望它也能够帮助你“驯服”软件复杂性。不要害怕Java或者括号!它们实际上都相当“驯良”。弗朗西斯·阿维拉

第1版序言

我可以告诉你,我有多享受极客的生活;我可以告诉你,1985年父亲给我展示穿孔卡时,我有多么着迷;我可以告诉你,我在7岁时是如何得到第一台计算机的;我还可以告诉你,我从1989年起就爱上了编程。我可以告诉你关于这一切的许多事情,但是不能肯定它们是否有趣。

作为替代,还是让我告诉你们我对答案的追求吧。多年以来,关于我们所在行业的一些问题一直困扰着我:为什么从来没有一个软件项目像它应有的那样简单?为什么没有一个项目能够按照时间和预算完成?为什么程序中总是有bug?为什么软件从来不按照人们的意图工作?为什么对软件进行更改总是那么困难?为什么不管一个项目开始时有多么清晰的计划,却总会变成一个“大泥球”?

几乎每个人都会认识到这些问题,但他们似乎接受这种现状。行业中的大部分人通过在时间安排和预算上设置缓冲以及接受普通水平的软件来应对它们。难道没有更好的办法了吗?

本书不是答案,绝对不是,但它是我向前探索的一部分。我的想法是,工具越好,越能够帮助我们创建好的软件。

这就引出了显而易见的问题:什么是更好的工具?它们在哪方面表现更好?我的答案便是,好的工具是能够更好地帮助控制复杂性的工具。毕竟,复杂性是我们所在世界中一切状态的根源。确实,Fred Brooks早在1986年就在一篇文章中提到了复杂性。他提出了基本复杂性和附带复杂性之间的区别。基本复杂性是问题领域中固有的,而附带复杂性是由问题领域之外的事物引入的。例如,在处理纳税申报的软件项目中,由复杂的纳税编码引入的复杂性是问题领域的一部分,因此其是基本复杂性。而由错综复杂的访问模式引起的任何复杂性则是附带复杂性。

那么,让我来改变一下措辞:好的工具能帮助我们减少附带复杂性。这些工具让我们尽可能好地完成工作,而且不会成为前进道路上的障碍。另外,出色的工具更不止于此,会为我们提供各种手段,提高设计人员和编程人员的效率,并且自身不会引入问题。Lisp编程语言就是为了成为这样的工具而被设计的。Clojure则是设计极其精巧的Lisp。

每个遇上Lisp的程序员都有自己的故事,我的故事和许多人类似。我的职业生涯从Java开始,最终撞上了一堵自己建立的墙。于是我开始探索动态语言,它们看起来更有表现力和可塑性。我喜欢使用Python和Ruby,并用它们编写了多个重要应用。当时我在一家名为ThoughtWorks的公司工作,有许多志同道合的同事。最终,其中一个人带领我转向Common Lisp。对这种语言的理解越深,我就越强烈地意识到其他语言的粗糙。我在几个个人项目上使用了Common Lisp,但是从未将其应用于重大项目。不过,它对我使用其他所有语言的编码工作都产生了深远的影响,我不断地寻找机会,试图将Lisp应用于现实世界的项目中。

我在2008年终于得到了机会。那时,我搬到加州湾区,加入了一家初创企业Runa的创建团队。按照真正的硅谷传统,我们的第一个办公室在创始人的车库里。我们希望凭借Runa公司的力量搅乱电子商务领域,想法是收集大量数据,用机器学习技术完全领会它们,然后提出个性化的交易,实时选择购物者。为了完成这些工作,我们必须攻克真正的技术难关。该系统必须每秒处理数千个请求,每天处理数太字节(TB)的数据;它还必须能够通过一组高级的陈述性领域特定语言(DSL)来编写脚本;它必须支持代码热交换,以便在运行中更新;它必须运行于云上,还必须完全由API驱动。我们不得不在缺乏资源的情况下完成该系统,因为设计团队只有三个人。

由于这些约束,我们需要一种能够提供“杠杆”的语言。因此,我们转向名为Clojure的新语言。这是一种运行于JVM之上的现代化函数式语言,它还承诺解决并发多线程代码中固有的问题。而且,它还是Lisp语言的一个变种!

我曾是这家初创企业的架构师,现在是工程副总裁。我将未来的成功押在这个名不见经传的人创建的新型编程语言(当时还在预发行阶段)上。但是我所读到的有关它的一切都与我产生了共鸣——一切都是那么合适。从那时起,我们使用Clojure取得了难以置信的成功。我们的团队在过去三年中成长了起来,但是仍然比类似公司的其他团队小一个数量级。我怀疑那些团队使用的是旧的Java。过去的经验让我坚定了信念,在其他条件相同的情况下,工具非常重要,而有些工具远优于其他工具。

当我们开始着手工作时,我曾将Clojure当成秘密武器——但是Clojure的社区很强大,并且是相互扶持的,因此将其作为一个公开的秘密似乎是更好的想法。我启动了湾区的Clojure用户组,现在已经有数百名成员。已经有几十个人来参加过我们的会议,喜欢在会上听到的内容,并决定在自己的项目上使用Clojure。

出于相同的信念,我编写了这本书,分享使用Clojure的经验,希望可以说服你们中的一些人,不仅看到这些“括号”,还能了解Lisp语言的总体能力和Clojure的具体特性。希望你们能觉得这本书实用且有趣。阿米特·拉索尔

关于本书

从2011年本书第1版发行以来,软件工程领域已经有了许多变化。当时,Clojure刚刚发行了1.3版本,社区正在致力于1.4版本的开发。ThoughtWorks技术雷达已经将Clojure从“评估”推进到“试验”(https://www.thoughtworks.com/radar/languages-and-frameworks/clojure)。喜欢冒险的程序员和软件公司开始注意到这一点,但是用Clojure搭建重要项目的情况仍不多见。2015年年底,Clojure在编程领域已经有了一席之地。就连家喻户晓的沃尔玛和《消费者报告》杂志也将Clojure用于核心业务(http://cognitect.com/clojure#successstories)。Clojure现在已经站稳了脚跟,完全不再出现于ThoughtWorks的技术雷达上了。

即使在Clojure仍然被边缘化的领域,它的核心思路——不可变性和函数式编程——也已经声名远播并结出了硕果。受到Clojure启示的不可变数据库Datomic正在被越来越多的人采用。Java 8现在拥有了Lambda:用于高阶函数编程的匿名内联函数。在许多不同的编程语言中,现在可以找到多个不可变数据结构库。这些思路已经通过ClojureScript(2011年10月才发行的!)和Facebook React UI框架的协作,在JavaScript中掀起了革命。不可变性和函数式编程现在已经成为主流的思路。

为了应对文化中的这些变化,本书第2版缩小重点范围,面向更广的受众。越来越多Java生态系统之外的程序员听说了Clojure,并有兴趣学习它,所以我们扩充了入门章节的内容,假定读者拥有较少的Java知识,更加鲜明地强调Clojure的哲学思想,这些都可以在任何语言中实践,并为它们带来益处。随着受欢迎程度的大幅提高,用于常见编程任务的不同程序库和在线教程激增。因此,我们删除了处理数据库连接、构建Web服务等任务的实操章节。这些章节都已经随着程序库和替代方法的成长而变得老旧,如果我们用现代工具和技术重新编写它们,毫无疑问在出版之前它们就又过时了。幸好,在软件工程的任何子领域中,找到使用Clojure的最新文档都不再困难。

简而言之,我们已经不再需要像第1版中那样卖力地倡导Clojure。如果你打算阅读本书,可能已经知道它是受Lisp启发并在JVM基础上构建的一种强大的通用函数式语言。你已经听说了这样的故事:规模很小的Clojure团队构建强大的分布式系统所花的时间比使用其他语言的大型团队还要短得多。你打算阅读本书,正因为你想要了解Clojure是如何实现这样的可能性的,也想知道如何达到同样的目标。如何使用本书

学习Clojure对许多程序员来说是一个飞跃。完全不同的语法、从命令式编程到函数式编程、不可变性、宏系统……这一切都令人畏缩。本书采用一种缓慢而稳健的方法来帮读者学习这种语言和各种概念,并且假定读者之前没有任何Lisp或者函数式编程语言的经验。首先是必备的基础知识,然后慢慢地分层次介绍这种语言的不同特性,并以直观的方式将它们组合起来。本书对所有主题都采用基本原理方法,首先解释某件任务必须以某种方式完成的原因,然后再讨论Clojure方法。

一旦完成了基础知识的学习,本书将介绍由多位编程者编写的规模更大的“严肃”Clojure程序所必需的特性、概念和技术。你将了解有效管理可变状态、大规模使用高阶函数编程、创建多态类型和抽象的同时平衡表达能力及性能、编写测试驱动Clojure程序、编写领域特定语言的方法。为了最大限度地利用本书,我们假定你熟悉某种面向对象(OO)语言,如Java、C++、Ruby或者Python,但是不需要任何Java、Lisp或者Clojure背景。路线图

本书共11章,下面描述每章的重点内容。

第1章简要介绍Clojure语言及其三大支柱:使用不可变数据结构的函数式编程、Lisp语法以及与Java的互操作性。

第2章介绍REPL(读取-求值-打印循环,这是Clojure的命令行解释程序),帮助你开始编写Clojure代码。本章包含对函数定义、流程控制和内建数据结构的纵览。

第3章讲解Clojure更独特的特性:元数据(为其他数据提供注解的数据)、异常处理、高阶函数(作为其他函数参数的函数)、两组作用域规则(词法和动态)、组织代码的命名空间、可以轻松简洁地将嵌套数据结构各部分放入变量的解构语法、为代码添加新字面语法的读取器字面量。本章中的许多方法可能与你所习惯的方法不同,但是在本章的最后,你将能够读写最简单的Clojure程序。

第4章讨论三种基本的多态性和使用多重方法时各种多态性在Clojure中的表现。如果你来自Java/C++世界,那么这将是一种大不相同的方法。Clojure的多重方法是实施多态行为的极度开放式方法,它们将方法分派的控制直接交给程序员。

第5章介绍Clojure与JVM的结合。没有一组强大的程序库,任何编程语言都不可能取得成功,Clojure巧妙地回避了这个问题。你可以非常轻松地在程序中使用任何Java程序库,从而可以立刻利用数千种久经考验的框架及程序库。Clojure还可以利用Java技术栈。在这一章中,你将学习从Clojure中使用Java代码、从Java中使用Clojure代码以及编写定义或者扩展Java类的Clojure程序的方法。

第6章解释Clojure的状态管理和并发方法,以及四种基本的并发原语。同样,这是处理可变状态问题的一种新方法。Clojure配备了极端高效的不可变数据结构,实现了类似数据库的STM(软件事务内存)系统。这种组合使该语言提供了对正确、安全和无死锁并发的内置支持。这很重要!你的程序可以利用多个核心,而不会产生传统多线程代码的相关问题。

第7章关注Clojure不同于大部分其他编程语言的另一种特性,这就是宏系统(不要和C语言的宏以及类似概念混淆)。Clojure本质上为代码生成提供了语言级支持。它的运行时系统中有一个钩子(hook),允许程序员以任何方式变换和生成代码。这是一种难以置信的强大功能,模糊了语言设计人员和应用编程人员之间的界限,使任何人都可以为该语言增加特性。

第8章深入介绍函数式编程范式以及第3章中涉及的高阶函数的使用方法。你将创建如下核心高阶函数的自有版本:map、reduce和filter。你还将全面理解函数的部分应用和局部套用。最后,你将在Clojure基础上构建自己的面向对象编程(OOP)系统,并忘掉Clojure与面向对象范式相关性的忧虑。实际上,你将不再用相同的方式去思考面向对象方法。

第9章讨论表达问题,其基础是第4章中研究的多态性。你将首先回顾这个老问题的概念,然后使用Clojure的多重方法以优雅的方式解决它。接着,在介绍其他Clojure特性(协议、记录和类型)之后,我们将告诉你一种有局限但性能更好的解决方案。

第10章说明如何将编写测试驱动代码的过程与第2章中介绍的Clojure REPL相结合,从而显著提升效率。本章还介绍模拟和打桩函数,以实现更好的单元测试策略。

第11章是本书的最后一章,重点是高级宏和DSL,建立在第7章所学知识的基础上。本章将引领你完成整个周期:从寻找最小化附带复杂性的工具开始。Clojure允许你通过宏系统根据自己的意愿改变这种编程语言,本章会深入介绍这一功能。你将设计一个内部DSL,作为使用DSL驱动Clojure应用中核心业务逻辑的一个例子。代码约定和下载

许多代码清单中有注释,以便强调重要的概念。在某些情况下,所用编号与代码清单后的解释关联。

下载和安装Clojure的指南参见附录。你可以在manning.com/books/clojure-in-action-second-edition网站上找到本书中所有例子的完整代码。

致谢

我们要感谢Manning Publications中帮助本书出版的每个人,包括为我们提供机会撰写本书修订版的Erin Twohey和Michael Stephens,在该过程中仔细审读手稿的Karen Miller,提供专业性技术编辑的Joseph Smith,以及为本书出版提供指导意见的Kevin Sullivan、Jodie Allen、Linda Recktenwald和Mary Piergies。

我们还要感谢在该过程中多次阅读各个章节并提供宝贵反馈意见的评审人员:Bruno Sampaio Alessi、David Janke、Fernando Dobladez、Gary Trakhman、Geert Van Laethem、Gianluigi Spagnuolo、Jeff Smith、Jonathan Rioux、Joseph Smith、Justin Wiley、Palak Mathur、Rick Beerendonk、Scott M.Gardner、Sebastian Eckl和Victor Christensen。

还要感谢我们的MEAP(Manning Early Access Program)读者,他们在“作者在线”论坛上发表了评论和修正意见。感谢他们对本书感兴趣并提供支持。

最后,我们要感谢提出不可变性的Rich Hickey,感谢他创造了Clojure并鼓励我们更简洁地编程。阿米特·拉索尔:

在一家初创公司工作(而且有了第一个孩子)的同时写一本书绝对不是放松的妙方!如果没有忍耐力超群的妻子Deepthi的支持,我绝对无法完成这两个版本。当我毫无进展的时候,只有她的鼓励能帮助我坚持下去。感谢你,亲爱的,没有你我绝对无法完成这个项目!

我还要感谢我的父母,在许多年之前,他们给了我走上这条道路的机会。我在印度长大,当时计算机还是幻想中的东西,大部分人都无法接触。父母贷款为我买了一台计算机,而不是购买他们的第一辆车,没有它就没有我的今天。因此,我要向父母说一百万次“谢谢”!

我还要感谢Ravi Mohan,感谢他在2001年指引我了解Lisp并阅读Paul Graham的论文。感谢他为我展示了这条道路的魅力!我想,还要感谢Paul Graham,他启发了我们中许多人的灵感。

感谢Runa的伙伴们让我写这本书。公司的创始人Ashok Narasimhan对整个项目都极其支持。我的其他同事也很支持我。尤其要感谢Kyle Oba和George Jahad的反馈和鼓励。

最后,我要特别感谢Siva Jagadeesan以各种各样的方式支持这项工作,帮助我将这本书升级为第2版。弗朗西斯·阿维拉:

首先,我要感谢阿米特·拉索尔,他编著的本书第1版对我进入Clojure世界有重要意义。我对本书所做的只是重新“装修”的工作,坚固的支柱都是阿米特建造的,他所写的那些内容仍然是本书的真正基础。

我还必须感谢妻子Danielle,她鼓励我接受Manning的邀请,合作编著本书的第2版,在我写作时她常独自在长夜中陪伴新出生的女儿,并认真地为我审读稿件。感谢你的爱和支持,以及对我痴迷于那些奇怪的“括号”的宽容。

我还要感谢Breeze EHR,这家小型初创企业促使我进入神奇的Clojure世界。特别感谢公司的创始人、核心Tyler Tallman,他将我从PHP中带出来。每次他与我分享激动人心的新想法时,我总是表现出坏脾气,对此我深感抱歉。第1章 Clojure简介

本章内容:

·Clojure是Lisp的一个变种

·Clojure是一种函数式编程语言

·Clojure以Java虚拟机(JVM)为宿主

·Clojure的关键特性和优点

任何足够复杂的C语言或者Fortran程序中,都包含一个临时特设的、不合规范的、充满程序错误的、运行速度很慢的、只有一半功能的Common Lisp实现。——Philip Greenspun(http://philip.greenspun.com/research/)1.1 Clojure的概念以及采用的原因

Clojure是一种简单、精炼的编程语言,其设计目的是轻松地同时利用遗留代码和现代化多核处理器。它的简单性来源于稀少而有规律的语法,精炼则源于动态类型和“函数即值”(也就是函数式编程)。这种语言可以轻松地使用现有的Java程序库,因为它以Java虚拟机为宿主。最终,它通过使用不可变数据结构和提供强大的并发结构简化了多线程编程。

本书介绍的是Clojure 1.6版本。在前几章中,你将学习Clojure的基础知识:语法、构件、数据结构、Java互操作性和并发特性。随着基础知识的学习,你将了解Clojure是如何用宏、协议和记录以及高阶函数简化较大程序的。在本书的最后,你将理解Clojure为什么很快就受到欢迎,以及它是如何改变软件开发方法的。

Clojure的优势不只体现在一个方向上。一方面,它设计为一种托管语言,利用JVM、Microsoft公共语言运行时库(CLR)和JavaScript引擎等运行平台的技术优势,同时增加了动态类型语言的“简洁性、灵活性和效率”(http://clojure.org/rationale)。Clojure的函数式编程特性包括高性能的不可变数据结构和用于处理它们的丰富API,可以生成更简单的程序,这些程序更容易测试和推导。无处不在的不可变性还在Clojure的安全、精确定义的并发和并行结构中起到核心作用。最后,Clojure的语法来源于Lisp传统,这为它提供了简练而强大的元编程工具(http://clojure.org/rationale)。

上述几点可能立即引起正面或者负面的反应,例如,你偏好的是静态类型语言还是动态类型语言。其他语言设计决策可能也不完全清晰。什么是函数式编程语言?Clojure是否和你已经见过的其他此类语言类似?Clojure是否拥有对象系统或提供类似于主流面向对象(OO)语言的设计抽象?在现有VM上托管这种语言有哪些优势和不足?

Clojure特性组合的前景在于,这种语言由简单、容易理解的部分组成,不仅为程序的编写提供强大的能力和灵活性,还使你可以无须理解语言的各个部分是如何组合的。别让任何人忽悠你:这种语言当中有许多需要学习的地方。用Clojure进行开发需要学习如何阅读和编写Lisp程序,愿意接受函数式编程风格,对JVM及其运行时库有基本的了解。我们将在本章介绍Clojure的这三大支柱,让你做好迎接本书其余内容(深入了解一种包含新旧两方面特性的难以置信的语言)的准备。1.1.1 Clojure:现代化的Lisp语言

在仍发挥着积极作用的语言系列中,Lisp是历史最悠久的之一(仅次于Fortran),Clojure是该系列中的一个新成员。Lisp不是一种特定的语言,而是1958年图灵奖获得者John McCarthy设计的一种编程风格。今天,Lisp系列主要包括Common Lisp、Scheme和Emacs Lisp,Clojure是最新加入的。尽管Lisp的历史断断续续,但它的实现(包括Clojure)用于各个领域的尖端软件系统:美国航空航天局(NASA)“探路者”任务规划软件、对冲基金算法交易、航班延迟预测、数据挖掘、自然语言处理、专家系统、生物信息学、机器人学、电子设计自动化、Web开发、下一代数据库(http://www.datomic.com),以及许多其他系统。

Clojure属于Lisp语言系列,但是它并没有追随任何现有的实现,而是组合多种Lisp的优势以及ML和Haskell等语言的特性。Lisp享有“黑科技”和“成功的秘密武器”的声誉,是条件、自动垃圾收集、宏和“函数是语言价值观”(不只是过程或者子程序;http://paulgraham.com/lisp.html)等语言特性的摇篮。Clojure以Lisp的这些传统为基础,采用务实的方法实现函数式编程,与JVM等现有运行时环境有着共生的关系,并拥有内置并发和并行支持等高级特性。

在本章的后面,当我们探索Clojure的语法时,你就能真正地感觉到Clojure是一种Lisp语言意味着什么,但是在我们进入细节之前,先考虑Clojure设计的其他两大支柱:Clojure是一种托管在JVM上的函数式编程语言。1.1.2 Clojure:务实的函数式编程

近年来,函数式编程(FP)语言的流行程度大增。Haskell、OCaml、Scala和F#从默默无闻中崛起,现有的C/C++、Java、C#、Python和Ruby等语言已经借鉴了因这些语言而流行起来的特性。由于社区中的这种活动,很难确定函数式编程语言的定义。

成为函数式语言的最低要求是,不仅将函数当成执行代码块的命名子程序。在FP中,函数就是值,就像字符串"hello"和数字42都是值一样。你可以将函数作为参数传递给其他函数,函数也可以将函数作为输出值返回。如果一种编程语言可以将函数当成值处理,它往往就被称为拥有“第一类”函数。此时此刻,这些概念似乎不可能成立,或者过于抽象,所以只要将它记在心里,在本章稍后你将会看到代码示例中的函数以有趣的新方式使用。

除了将函数当成第一类值之外,大部分函数式语言还包含如下特有特性:

·具有引用透明性的纯函数

·默认的不可变数据结构

·对状态的受控、显式更改

这三个特性是相互联系的。FP设计中的大部分函数是纯粹的,这意味着,它对周围的世界没有任何副作用(如更改全局状态或者进行I/O操作)。函数还应该具备引用透明性,也就是说,只要函数的输入相同,它就始终返回相同的输出。从最基本的层面上讲,具有这种表现的函数很简单,对有一致表现、与运行环境没有关联的函数代[1]码进行推导更简单易行。将不可变数据结构作为语言的默认状态保证了函数不会修改传递给它们的参数,从而更容易编写纯粹的引用透明的函数。简单地说,参数似乎总是按值传递而不是按引用传递的。图1-1 xs值的树形表现。在https://commons.wikimedia.org/wiki/File:Purely_functional_tree_before.svg许可下使用

你可能会说:“等等,在所有地方都按值传递参数和复制数据结构代价太高了,我需要改变我的变量值!”Clojure的不可变数据结构是以用于避免高代价复制的高性能纯函数式数据结构实现的研究为基[2]础。理论上,如果你对不可变数据结构进行更改,其结果是一个全新的数据结构,因为你不能更改不可变的东西。在现实中,Clojure隐含使用结构化共享和其他技术,确保执行的复制次数最少、不可变数据结构上的操作快捷且节约内存。实际上,你可以同时得到按值传递的安全性和按引用传递的速度。

不可变数据结构不能更改,但是图1-1和1-2中的框图展示了“编辑”不可变数据的方法。图1-1中的树xs包含不可变节点(带圆圈的字母)和引用(箭头),所以不可能添加或者删除树xs中的值。但是,你可以创建一棵新树,并尽可能多地共享原始树xs中的内容。图1-2展示了添加一个新值e的方法:在通往树根的路径上创建一组新的节点和引用(d',g',f'),重用旧的节点(b、a、c和d),从而得到新的不可变树ys。这是Clojure不可变数据结构的基本原理之一。图1-2 新树ys的表示。在https://commons.wikimedia.org/wiki/File:Purely_functional_tree_after.svg许可下使用

但是,你的程序中一切都变了。大部分编程语言具有作为命名状态的变量,你可以在任何时候对它们进行修改。在Clojure中,这种做法更加受控,定义也更加明确。实际上,像数字42这样的“值”是不能更改的;42就是42,42减去2并不会改变42这个数字,而只是给出了一个新值40。这一事实可以延伸到所有值,而不仅是数字值。另外,如果你有一个变量作为程序中某个事物的身份标识,该变量的初值为42,那么你可能想要在后面的程序中为该变量赋一个新值。在这种情况下,变量就像一个容器,你可以在不同时点放入不同的值。在多线程并发的世界,你的编程语言应该为那些更改发生的形式提供保证,Clojure正是这么做的。

Clojure让你更改变量中保存的值,但是采用明确定义的语义,这些语义与更改发生的方式和时间有关。如果你有一个变量,希望更改其值,则Clojure让你以原子方式进行,这样你就可以确定,如果多线程执行查看一个变量的值,则这些线程总是得到一致的情况,当变量[3]更改时,以一次原子操作进行。如果需要将多个变量当成一个单元同时更改,则Clojure有一个单独的机制,用它的软件事务内存(STM)系统将多个变量当成一个事务的一部分进行更改,如果更改没能按照预期完成,则回滚所有更改。如果需要更改一个变量,但是希望更改发生在某个单独的线程执行中,以免阻塞程序主线程,则Clojure也提供了相应的机制。所有这些都内置于该语言的内核,使并发性的实现变得非常容易,只有在你希望程序不支持这种特性时才需[4]要做额外的工作。

函数式语言往往根据函数的“纯粹性”或者是否严格坚持函数式编程语言设计的理论基础来判断。一方面,Clojure的默认使用模式鼓励纯函数式编程:不可变数据结构、高阶函数和代替强制循环的递归,甚至可以选择集合的惰性求值和及早求值。另一方面,Clojure很务实。尽管大部分问题可以用不可变数据结构和函数式编程模式解决,但某些任务用可变状态和更像命令式编程的方法建模会更清晰一些。正如刚才所描述的那样,Clojure提供了一些结构,它们具有精确定义用于共享状态和随时更改的语义。此外,Clojure也不像某些“更纯粹”的函数式编程语言那样,要求开发人员注释会产生副作用的代码,不管这些代码是更改状态、打印到屏幕还是进行网络I/O。

Clojure的另一部分务实特性来自于它的托管式设计。在必要的时候,你总是可以回到宿主平台,直接从Clojure使用Java API,从而具备来自Java直接编码的所有性能(以及陷阱)。[1] 要理解简单性在Clojure 设计考虑中的独特作用,可参见题为Simplicity Ain’t Easy 的谈话:http://youtu.be/cidchWg74Y4。对于更深入、更抽象、不完全以Clojure 为中心的“容易与简单”区别演示,请观看Clojure 创始人Rich Hickey 的Simple Made Easy:http://www.infoq.com/presentations/Simple-Made-Easy。[2] Chris Okasaki 的《Purely Functional Data Structures 》(1996)论文可以在http://www.cs.cmu.edu/~rwh/theses/okasaki.pdf 下载。[3] 在这种情况下,“原子性”是“不可见”的同义词。如果操作是原子操作,那么任何其他操作都不会干扰正在被改变的底层状态。如果任何其他进程企图在原子操作期间获取变量状态,则只会得到原子操作开始之前最后一个变量值。当其他进程企图在原子操作期间更改底层状态时,它们将被延迟到原子操作完成之后。[4] 对于那些熟悉Clojure的人,请注意此时随意地用术语“变量”介绍Clojure处理值、身份和底层状态的独特处理方法以及它们的所有变化。我们将在后面的一个章节中用准确的Clojure术语介绍并发结构的细节。1.1.3 JVM之上的Clojure

Clojure设计为一种托管语言。虽然大部分编程语言项目都将语言设计与一种搭配的运行时平台相结合,Clojure创始人Rich Hickey却决定专注于Clojure语言,依靠现有的虚拟机作为其运行时平台。他从JVM开始入手,但是Clojure现在已经凭借与.NET生态系统的互操作性扩展到CLR上(Clojure-CLR),此外还扩展到浏览器和服务器端JavaScript引擎(ClojureScript)。

在做出这一决策时,Rich牢记在工程中最好保持“懒惰”的原则(http://blog.coding horror.com/how-to-be-lazy-dumb-and-successful/)。JVM是一种普遍使用的成熟平台,具有大量第三方程序库。权威的HotSpot JVM实现是开源项目,配备先进的即时(JIT)编译器,可以选择垃圾收集程序,以用于各种用例的“原生”运行时[1]环境保持有竞争力的性能。将这些特性作为底层运行时主机理所当然的一部分,Clojure社区就可以腾出手来,专注于稳固的语言设计和更高级的抽象,而不需要重新研究VM设计(那会带来新的bug)。

从业务的角度看,依赖现有的VM降低了引入Clojure的风险。许多组织都有与JVM或者CLR相关的现有架构和专业人员,将Clojure作为更大规模的Java或者C#应用的一部分引入的能力是一个有力的卖点。Clojure在JVM上编译为字节码,在CLR上编译为通用中间语言(CIL),这意味着它是所在VM上的“头等公民”。

另外,Clojure有意地不规避你与它所在的宿主平台接触。为了在JVM上高效地使用Clojure,你必须学习它的运行时环境,至少包括如下知识:

·Java的核心类java.lang.*及其方法

·JVM的线程/进程模型

·JVM如何在其类路径(classpath)上寻找需要编译的代码

我们将在本章中介绍这些必备的Java和JVM概念,并在遇到更高级主题时加以解释,所以你无须放下本书而先去学习Java。如果你对在CLR或者JavaScript引擎上使用Clojure感兴趣,就需要对那些平台有同样的了解,才能有效地使用Clojure。

现在,你对Clojure是JVM上的函数式Lisp语言已经有了粗略的理解,那么开始编写一些Clojure代码以体现这些概念。[1] JVM性能特征的概述可参见有关Java性能的维基百科词条:http://en.wikipedia.org/wikiJava_performance。1.2 语言基础知识

Cloure的Lisp、函数式编程和JVM特性是密不可分的,在每一步中,它们都相互作用,共同讲述一个引人入胜的软件开发故事,但是因为我们必须从某个地方入手,所以介绍语法是很好的出发点。1.2.1 Lisp语法

Clojure的语法源自Lisp:有许多的括号。这对在Algol衍生语言(如C、C++、Java、Python、Ruby、Perl等)方面有经验的大部分开发人员来讲都很陌生。因为这种做法很奇怪,所以我们将采用一些技巧来对付括号带来的烦恼:

·首先忽略所有括号

·考虑其他语言使用括号的方式

·将括号看成“值单元”或者表达式

·接受括号

为了说服你在一开始忽略所有括号是可行的,我们首先来看一些代码示例:

如果你猜到这是向URL http://example.com发出一个HTTP请求,那么你是正确的。get-url函数在Clojure中不是默认定义的,但是它采用了一个很好的自我描述函数名,在介绍完基础知识之后,我们将以此作为主要示例之一。让我们来看看使用Clojure内置函数的一些代码示例,并观察它们的输出:

str函数是string(字符串)的简写,将其参数连接为一个输出字符串,其他语言通常使用+之类的运算符,那么,用运算符连接多个字符串的代码是什么样的?

这称为中缀符表示法(infix notation),因为你将运算符放在要连接的每个字符串之间。作为Lisp语言的一种,Clojure对所有函数甚至类似运算符的一切都使用前缀表示法,所以如果你想要连接不止两个字符串,只需要不断地向str函数传递参数即可:

如果需要进行算术运算,同样的原则也适用:

这些例子展示了Clojure方法的两种优势。首先,函数和运算符之间没有差别,因为Clojure没有运算符。不需要记忆运算符的优先级。str和+形式都是正规的Clojure函数,只是其中一个恰巧使用非字母字符作为其名称。其次,因为你不需要在参数之间插入运算符,所以这类函数自然可以取任意数量的参数(称为可变参数数量),你可以添加更多参数,不需要担心忘记在每两个参数之间放入运算符。

在前面的几个例子中,你可以安全地省略括号,但是让我们来提高点难度。如果你需要进行超过一种运算,那么在使用算术运算符的语言中,你可以这么写:

在使用运算符的语言中,你需要记住+和*运算符的优先级,但是不管使用哪一种语言,都可以用表达式之外的括号来消除歧义:

Clojure没有运算符,所以明确性水平就成了必要条件:

让我们逐个表达式地分解上述例子。

最外面的函数是+,该函数有两个参数:3和(*42)。你知道3的全部含义,所以让我们来解析(*42)。如果用参数4和2调用乘法函数*,将得到结果8。让我们重写这个表达式,首先解析(*42)这一步,将重要部分用粗体表示,以吸引人的注意力:

现在,你有了一个+函数和两个简单参数,总和很明显是11。虽然在其他语言中利用运算符和优先级规则编写这种算术表达式更简明,但是Clojure使整个语言中的函数调用完全一致。

现在,你已经看到了一些括号,让我们暂时停止忽视它们,理解它们的主要用途。1.2.2 括号

Lisp括号的用法是它语法上的秘密武器,但是我们目前还不想深入介绍其目的。为了读写你的第一批Clojure程序,我们将说明括号的两个目的:

·调用函数

·构造列表

目前的所有代码都展示了第一种用途的例子——调用函数。在一组括号中,第一种语言形式总是函数、宏或者特殊形式,后续的形式是其参数。图1-3是括号的这种用法的一个简单示例。我们将在遇到宏和特殊形式时介绍它们,现在你可以将它们视为得到特殊处理的函数。图1-3 用于调用函数的括号

开始训练你的大脑,将左边的括号与函数调用关联起来。左括号就像放在函数耳朵边的电话,做好准备用到对应的右括号为止的其余项目“呼叫”(调用)它。一旦我们开始研究高阶函数编程模式,将这种关联深植于心中就更加重要了。还要记住,函数的参数不总是简单值,正如在前面的例子中看到的,还可以是嵌套的表达式——图1-4中有一个例子。图1-4 用于调用函数的嵌套括号

括号的第二种用处曾经最常见,但最引不起人们的注意——构造列表。一方面,Clojure具有用于集合而非列表的字面语法,按照习惯,Clojure程序根据不同的性能优势使用各种集合类型。和其他Lisp系列语言不同,Clojure不以列表为中心,部分原因是它提供了其他集合类型的字面语法。另一方面,在元级别,你的整个Clojure程序就是一系列列表:程序的源代码被Clojure编译器解释成一些列表,这些列表包含函数名称和需要解析、求值和编译的参数。因为在较低的编译器级别和常规程序代码中都有相同的语言特性,所以Lisp实现了独特的强大元编程能力。我们将在后续章节中讨论Clojure宏时深入介绍这一事实的重要性,但现在我们将浏览Clojure的重要数据结构和集合类型,以便阅读实际的代码示例。

你现在已经了解了Clojure的基本语法:包含函数(或者表现类似函数的特殊形式)及其参数的括号。因为括号是该语言中所有表达式的容器,所以在编辑Clojure代码时可以像积木一样安排这些表达式,每一个表达式都是产生一致值的独立功能小世界,可以放在程序中任何需要该值的地方。而且,这种括号语法的一致性意味着IDE和文本编辑器能够提供结构化编辑,可以轻松地将表达式移到各处,也就是说,你从来不需要确认开始括号和结束括号的匹配。随着用于Clojure编程的时间越来越多,强烈建议你学习所选择的开发环境中的这些工具,这样,Clojure的括号将变成一种优势,而非障碍。1.3 宿主互操作性:JVM速成教程

Clojure不对程序员隐藏实现它的宿主平台。在本书中,我们将专注于Clojure的权威JVM实现,但是与宿主互操作(通常称作interop)的原则对Clojure的所有目标平台都是通用的。因为Clojure拥抱其宿主平台,而不是努力地隐藏它,所以你必须学习Java和JVM的基础知识才能够用Clojure编码。

Java由三个一起设计和交付的不同部件组成:一种语言,一个虚拟机和一个标准程序库。Clojure的各个部分使用Java语言编写,但是Clojure本身不使用它。相反,Clojure代码直接编译为字节码,供JVM运行。Clojure还要求你使用标准库提供的许多种基本函数。因为标准库是用Java语言编写并用于该语言的,所以Java语言的基本知识将有助于你更好地利用Java程序库。

在许多情况下,Clojure直接使用Java类型和标准程序库。例如,Clojure中的字符串是Java的String对象,数值字面量是Java的Long对象,Clojure的集合实现的接口与Java集合实现的接口相同。重用Java类型和接口带来了一个好处:Java代码可以无缝地使用Clojure类型(例如不可变数据结构)。

有时候,Clojure用自己的函数包装Java程序库的功能,就像Clojure的clojure.string命名空间中的许多函数将其功能委托给Java的String类中的方法。但很多时候没有这样的Clojure包装器,你必须直接调用Java方法。例如,Clojure没有实现用于数学计算的常规函数,如abs(绝对值)、exp(指数)、log、sin、cos和tan等在[1]java.lang.math类中可以找到的方法,因此必须通过本节后面介绍的Java互操作语法调用它们。

我们将简短地回顾Java的类型、类和对象系统,以便了解它对Clojure代码与Java互操作的意义。[1] Math类的API文档可以在http://docs.oracle.com/javase/7/docs/api/java/lang/Math.html上找到。1.3.1 Java类型、类和对象

Java是一种基于单继承类层次结构的面向对象语言。除了类以外,常见的行为可以组合为接口,接口作为方法签名的简单概述,实现它[1]们的类必须支持这些签名。在一个文件中只能定义一个公共(public)类或者接口,这些类必须放在Java类路径上的目录中。Java类路径类似于C的搜索路径或者Ruby的$LOAD_PATH,是一组目录。Java编译器在寻找作为程序一部分进行编译的文件时,将在这些目录上搜索。Java类或者接口的完全限定名称由包名后面跟上定义的类名或者接口名组成;例如,Java的Math类位于java.lang包中。这就允许不同的类使用相同的名称(例如Math),只要它们不在同一个包里,加载到JVM时就具有独一无二的完整名称(例如,java.lang.Math和com.mycompany.Math)。

这些和Clojure有什么关系呢?在所有Clojure程序中,默认加载Java的java.lang包中的所有类,这样你就可以引用String和Integer等,而无须输入java.lang.String和java.lang.Integer。许多Clojure数据结构(特别是集合)实现Java接口,所以,如果Java程序库使用实现那些接口的对象,就可以接受Clojure数据结构作为参数。例如,所有Clojure集合都实现java.lang.Iterable或java.util.Collection,而根据用途,只有一些集合实现java.util.List或java.util.Map。

和Java编译器一样,Clojure编译器将在Java类路径上寻找Clojure源代码,并预期命名空间的全名是独一无二的。附录A介绍了在文件系统上组织项目、设置类路径和调用Clojure编译器的方法细节。

虽然不得不学习一些Java基础知识,但是你由此可以获得大量经过考验的成熟Java程序库,这些都可以从Clojure程序中无缝使用:Joda Time提供准确的日期和时间操作;JDBC驱动程序输出与不同数据库通信的通用API;Jetty是一个先进的嵌入式Web服务器;Bouncy Castle有处理Java加密功能的简便API;Selenium WebDriver可用编程方式控制真正的Web浏览器,让你测试Web应用;不同的Apache Commons程序库提供各种各样的实用程序,可以作为扩展的Java标准库。除了应用程序库,你还可以使用所有内置工具监控JVM的性能、VisualVM、YouKit等外部剖析工具以及New Relic等剖析器服务,以得到对Clojure应用运行情况的更深入理解。

描述了这些通过Java互操作得到的精彩特性之后,我们还没有讨论如何从Clojure访问它们。Clojure如何区分常规的Clojure代码和完成Java互操作的代码?答案的第一部分是点运算符。[1] Java 8引入了接口的默认方法。因为在本书编著期间,Clojure当前以Java 6为最低目标版本,我们将继续将接口当成没有默认实现的简单方法协议。

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载