Clojure编程(txt+pdf+epub+mobi电子书下载)


发布时间:2020-08-26 05:13:06

点击下载

作者:埃默里克 (Chas Emerick)

出版社:电子工业出版社

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

Clojure编程

Clojure编程试读:

前言

Clojure是一种动态的、强类型的、寄居在Java虚拟机(JVM)上的语言,如今已经是它的第5个年头了。现今它已经被各种背景的程序员热情地应用到几乎所有的领域。Clojure提供了一些很引人注目的特性来帮助我们解决现代编程所面临的各种挑战:

● 函数式编程的基础,包括一套性能可以和典型的可变数据结构媲美的持久性数据结构。

● 由JVM所提供的成熟、高效的运行时环境。

● 跟JVM/Java的互操作能力使得很多架构、运维方面的需求可以得到满足。

● 一套提供并发、并行语义的机制。

● 是一种Lisp方言,因此提供了非常灵活、强大的元编程能力。

Clojure为那些受编程语言和环境限制而痛苦的程序员提供了一个实际可行的新选择。我们将会通过展示Clojure与大家每天都会使用的现有技术、类库或者服务的无缝交互来证明这一点。我们会逐步介绍Clojure的基础知识,从大家最熟悉的一些方面开始而不是从一些枯燥的原理开始。这本书的目标读者是谁?

我们在写这本书的时候,脑子里面是有一些目标读者的,希望你是其中之一。

Clojure跟你现在最喜欢的语言在表达力、简洁性和灵活性方面可以匹敌——通常还有所超越,同时还使你可以不费任何力气就能获得由JVM所提供的性能、类库、社区以及运维稳定性。这也使得Clojure 很自然地成为Java程序员(以及使用解释型、不是那么快的非Java语言的JVM程序员)的“下一代”语言,因为它们不想因为脱离JVM而导致写出来的程序性能下降,或者不想因此而放弃之前在JVM平台上的投资。而对于那些不想放弃语言强大表达力,但是又希望获得一个更可靠、更高效的平台以及更多可以选择的高质量类库的Ruby、Python程序员来说,Clojure也是很自然的选择。职业Java程序员

这个世界上有成千上万的Java程序员,但是有一小部分Java程序员工作在极限条件下解决十分重大的问题,通常是一些领域相关的问题。如果你就是这样的一个Java程序员,那么你可能一直在寻找着更好的工具、技术或者实践方法来提高工作效率以贡献更多的价值给你的团队、公司或者社区。而且你可能对于Java跟其他语言相比所表现出来的种种局限而苦恼,但是你舍不得离开JVM这个生态系统:它的流程成熟度、大量的第三方库、软件供应商的支持服务以及大量熟悉Java的程序员资源,即使其他语言的特性是那么诱人。

你会发现Clojure是一个很不错的选择。它以优异的性能运行在JVM之上,能够跟现在你已经有的所有的类库、工具以及应用进行交互,而且它比Java简单很多,同时又更富表达力、更简洁。Ruby、Python以及其他程序员

从任何一个方面来讲,Ruby和Python都已经不算什么新语言了,但是最近这些年获得了极大的关注(应该可以算是“主流语言”了吧)。不难看出其中的缘由:它们都是极富表达力的动态语言、有很活跃的社区、在很多领域都鼓励最大化程序员的效率。

Clojure将是你理想的“下一代语言”。作为一个Ruby或者Python程序员,你肯定不愿意放弃这些语言所提供的优秀特性,但是又想让你的程序在一个更强劲的平台上运行,可以有更好的运行时性能,更多的类库可供选择。高效寄居于JVM之上的Clojure完全满足这些条件,通常在程序简洁性以及工作效率方面更会超出你的期望。

在书中我们会经常把Clojure跟Java、Ruby以及Python进行比较来把一些概念“翻译”成你的专业领域中对应的概念。在这些比较中,我们比较的总是这些语言的权威实现:

● Ruby MRI(也称为CRuby)

● CPython

● Java 6/7如何阅读这本书

在规划整本书结构的时候,我们准备给大家介绍两方面的内容:一是有关Clojure语言的细节;另外就是一些实际的、大家都会碰到的实际问题的例子。但是我们努力避免那些不是很成功的规划办法。具体一点来说就是,我们避免以一个例子贯穿整本书,这种方式的结果是,很多时候例子跟要叙述的概念往往不是那么相关,而且这一个例子可能对于读者来说不是那么熟悉。

有了这个概念之后,我们把这本书分成了两部分,从最基本的概念开始介绍Clojure的语言细节,这部分大概占全书的三分之二;从第4部分开始介绍一些具体的、实际生活中的例子。这种清晰的分隔符合了“duplex book”的要求(这个概念最早是Martin Fowler在http://martinfowler.com/bliki/DuplexBook.html里面提到的)。因此,你可以以两种方式来阅读这本书。从Clojure的实际应用例子开始

学习一门语言最好的办法通常就是看怎么用它来解决实际的问题。如果你喜欢这种方式,那么希望在本书第2部分举的例子有跟你日常做的事情相关的例子,这样你可以自己做个比较:用你自己熟悉的语言怎么做这件事情,再看看书里介绍的用Clojure如何解决这个问题。在这个过程中你会遇到很多未知的概念以及语言结构,每当遇到这些问题的时候,你可以以当前的上下文作为线索去本书的第1部分查找对应的章节阅读。自底向上——从Clojure最基本的概念开始

有时候弄懂一个东西的唯一办法是从基本的概念开始学习,如果你习惯这种方式的话,那么从第1章的第1页开始读是最好的办法了。我们尝试以渐进的方式阐述Clojure所有基本的原则以及结构,这样做的结果是你很少需要先阅读书的后面章节内容才能理解前面章节的内容。而当你准备自己动手实践的时候,你可以到后面的实践部分挑一个你最感兴趣、跟你要做的事情最相关的例子进行阅读。我们是谁?

我们是通过不同途径认识并且喜欢上Clojure的三个程序员。在写这本书的时候,我们试图把我们对于为什么使用以及如何使用Clojure的经验告诉大家以让你也可以很好地使用它。Chas Emerick

Chas从2008年的早期开始就一直活跃于Clojure社区。他给Clojure语言核心贡献过代码,参与过十多个Clojure开源项目,并且经常会撰写或者演讲一些有关Clojure或者软件开发方面的东西。

Chas维护了Clojure Atlas(http://clojureatlas.com),这是一个以交互式的、可视化的方法帮助大家学习Clojure及其标准库的一个网站。

Chas创建了Snowtide公司(http://snowtide.com),它坐落于马萨诸塞州西部,Chas的主要领域在于非结构化数据的提取,并且特别擅长与PDF相关的数据提取。他经常在他的博客(http://cemerick.com)上发表一些有关Clojure、软件开发、企业管理等方面的文章。Brian Carper

Brian是一个从Ruby转过来的Clojure语言爱好者。他从2008年开始就使用Clojure了,不管是自己业余的小项目还是工作中的项目,涉及的领域从Web开发到数据分析再到桌面应用编程等。

Brian是Gaka(https://github.com/briancarper/gaka)的作者,这是一个把Clojure代码转换成CSS的编译器;他还是Oyako(https://github.com/briancarper/oyako)的作者,这是一个对象关系映射的类库。他在他的博客http://briancarper.net上发表了Clojure以及其他主题的一些文章。Christophe Grand

Christophe是一个迷失在Java世界的函数式编程爱好者,直到2008年遇到Clojure,一见钟情!他是好几个软件的作者:Enlive(http://github.com/cgrand/enlive)是一个HTML/XML转换、提取以及模板库;Parsley(http://github.com/cgrand/parsley)是一个递增式的parser生成器;Moustache(http://github.com/cgrand/moustache)是一个为Ring设计的路由及中间件应用的DSL。

作为一个独立咨询者,他开发并且提供Clojure培训。他在他的博客http://clj-me.cgrand. net上撰写了有关Clojure的文章。致谢

本书跟其他任何图书一样,如果没有那么多人不知疲倦的帮助的话是不会有这本书的。

首先要感谢Clojure语言的作者Rich Hickey。在短短的几年里,他设计、实现并且把Clojure引入了这个世界。对于很多人来说这并不只是另外一个工具而已,它重新激起了我们对编程的热爱。除此之外,他个人也教给了我们很多东西——当然是有关编程的,不过同时也学到了他的耐心、品格和眼光。多谢!Rich。

Dave Fayram和Mike Loukides对于本书雏形的形成起了至关重要的作用。当然,如果没有我们的编辑Julie Steele以及所有O'Reilly那些好人,大家现在也看不到这本书,他们帮我们搞定了出版过程中的一切事宜。

如果没有审校人员的把关,这本书不会有这么高的质量。审校人员有: Sam Aaron、Antoni Batchelli、Tom Faulhaber、Chris Granger、Anthony Grimes、Phil Hagelberg、Tom Hicks、Alex Miller、William Morgan、Laurent Petit以及Dean Wampler。同时我们也想感谢那些通过O'Reilly论坛、E-mail、Twitter等途径给本书早期预览版提出反馈和建议的读者们。

Michael Fogus和Chris Houser在很多方面给了我们灵感。其中之一就是他们在The Joy of Clojure一书中对于跟REPL交互内容的显示格式,我们直接照搬过来了。

如果这里少提了任何人,那么请接受我们默默的感谢以及诚挚的歉意,最后我们很高兴能把本书带给大家!最后,当然感谢得还远远不够

Clojure社区已经作为我的另外一个家好多年了。这个社区里面的成员都很热情,给我以帮助,是我的榜样。特别是经常在#clojure频道里出现的人们,不但跟他们成了好朋友,也从他们那里学到了从别的地方学不到的东西。

感谢我的合作者,Christophe和Brian: 跟你们一起撰写本书对我来说是莫大的荣耀。没有你们的话,我是绝对完不成这本书的。

感谢我的父母,Charley和Darleen: 我对于事物运作原理无法抑制的好奇心、我对于语言以及修辞的热爱、我对于商业的兴趣跟你们早年对我的影响是分不开的。没有你们影响的话,我是不会走上现在的路的: 创建自己的软件公司以及撰写这本书。

最后,感谢我的妻子Krissy: 为了让我能追求自己的理想你做出了那么多牺牲。此时说什么都显得那么苍白,我只说一句: 我爱你。—Chas Emerick,2012年2月

感谢所有帮助创建Clojure这门语言的社区成员:感谢你们不知疲倦的辛苦工作,你们的工作使得我的职业以及个人编程生活变得那么有趣,使得我的眼界更加开阔。

感谢我的合作者Christophe和Chas: 我从来没有跟比你们聪明的人工作过。跟你们一起工作我很荣幸。

感谢我的妻子Nicole: 我每天晚上打字使得你不能入眠。—Brian Carper,2012年2月

感谢Rich Hickey,你创建了Clojure并且培育了这么一个友好的社区。

感谢Clojure社区,你们帮助我提高了我的水平。

感谢我的合作者,Brian和Chas: 跟你们合作我很荣幸。

A mon professeur Daniel Goffinet, et à ses exercices improbables, qui a radicalement changé mon approche de la programmation et de l'informatique—sur ces sujets je lui suis plus redevable qu'à nul autre.(感谢Daniel Goffinet教授,他从根本上改变了我对于编程以及计算的看法——在这些主题上我最感谢他。)

A mes parents pour votre amour bien sûr mais aussi pour tout le temps à s'inquiéter que je passais trop de temps sur l'Amstrad.(感谢我的父母:感谢你们对我的爱以及帮我买的那个8-bit的计算机,你们还担心我在上面花太多时间呢。)

A ma compagne Emilie, et mon fils Gaël, merci d'être là et de m'avoir supporté pendant l'écriture de ce livre.(感谢我的妻子Emilie和我的孩子Gaël:感谢你们在我写作过程中对我的支持。)—Christophe Grand,2012年2月本书使用的一些格式约定

我们在本书中使用如下一些约定:

斜体(Italic)

表示一个新词组、URL、邮件地址、文件名或者文件扩展名。

等宽字体(Constant width)

用来表示程序代码,也会用在一个普通段落中用来引用变量或者函数名、数据库、数据类型、环境变量、表达式以及关键字。

以分号( ; )开头的行

表示这行文字是由在REPL中运行的代码打印出来的(也就是说,打印到标准输出或者标准错误)。

以分号+等号( ; = )开头的行

用来表示这是REPL中运行的代码的返回值。

加粗的等宽字体(Constant width bold)

表示命令或者其他一些用户应该原封不动地输入的文本。

斜体的等宽字体(Constant width italic)

表示这个文本应该被替换成用户提供的值或者它的值是由上下文确定的。

这个图标表示提示、建议或者一般的标注。

这个图标表示警告或提示。

中文版书中切口以“”表示原书页码,便于读者与原英文版图书对照阅读,本书的索引中所列的页码为原英文版页码。示例使用

本书的宗旨就是帮助你完成工作。一般而言,你可以在自己的程序和文档中随意使用书中的代码。除非你原样引用大量代码。否则无须联系我们获得授权。例如,在编写程序时引用了本书若干代码片段是无须授权的,然而销售或分发O'Reilly图书示例光盘则是需要授权许可的。通过引用本书内容以及代码的方式来答疑解难是不必有授权的。而将书中的代码大量加入到你的产品以及文档中,则需要授权。

如果你在引用书中内容时注明出处,我们将不胜感激,但是这不是必需的。引用声明通常是包含了标题、作者、出版商和ISBN。例如:“Clojure Programming by Chas Emerick, Brian Carper, and Caristophe Grand (O'Reilly). Copyright 2012 Chas Emerick, Brian Carper, and Christophe Grand, 978-1-449-39470-7.”

如果你发现自己对书中代码的使用有失公允,或是违反了前述条款,敬请通过permissions@oreilly.com与我们联系。Safari® Books Online

Safari Books Online是一个按需索取的电子图书馆,让你能够轻松地在超过7500本技术及创新参考书和视频中进行检索,快速找到你需要的答案。

订阅之后,你可以在我们的在线图书馆中阅读图书并观看视频;在你的手机或移动设备上读书;在印刷前看到新标题,独家阅读撰写中的原稿,向作者进行反馈;复制粘贴代码示例,管理你的收藏,下载章节内容,在关键部分加上书签,添加笔记,打印页面,并从大量其他省时的特性中获益。

O'Reilly Media已将本书英文版上传至Safari Books Online。要想访问本书或其他来自O'Reilly和其他出版商的类似主题,可以在http://my.safaribooksonline.com进行免费注册。如何联系我们

请将对本书的评价和存在的问题通过如下地址告知出版者:

美国:

O'Reilly Media, Inc.

1005 Gravenstein Highway North

Sebastopol, CA 95472

中国:

北京市西城区西直门南大街2号成铭大厦C座807室(100035)

奥莱利技术咨询(北京)有限公司

O'Reilly的每一本书都有专属网站,你可以在那找到关于本书的相关信息,包括勘误列表、示例代码以及其他的信息。本书的网站地址是:

http://shop.oreilly.com/product/0636920013754.do

对于本书的评论和技术性的问题,请发送电子邮件到:

bookquestions@oreilly.com

关于本书的更多信息、会议、资料中心和O'Reilly网络,请访问以下网站:

http://www.oreilly.com/

http://www.oreilly.com.cn/第1章进入Clojure仙境

如果你正在读这本书,那么说明你已经准备学习一门新的语言了。同时你也应该意识到,这会花费你一些时间和精力在上面,作为回报,它会使你工作更高效,使你的团队工作更高效,使你的公司更有生命力、竞争力。

我们相信你在学习、使用Clojure的过程中会有一种互相反馈的感觉:Clojure要求你提高自己的水平,而在你学习Clojure的过程中,它也会让你的水平越来越高。

作为软件开发人员,我们会有各自钟爱的一些工具和语言。到底使用哪个工具和语言来做一个项目很多时候是由工具或者语言的实用程度以及遗留系统决定的。但是有一些东西是共通的:我们都希望使用那些能够最大化生产力,使得我们可以编写出有价值的、优雅的软件系统的工具。总结一句话:希望我们的工具可以让容易的事情保持容易,让困难的事情变得可能。为什么要选择Clojure?

Clojure就是一门符合上面那个标准的语言!它融合了各种优秀语言的最好的特性——包括各种Lisp的实现,Ruby、Python、Haskell,等等。Clojure提供了一些工具来解决程序员需要面对的最头痛的一些问题。同时Clojure不会让你做太大的改变——你无须学习新的架构或平台,Clojure是架构在JVM平台上的,而JVM平台是当前用得最多、最广泛的平台。

为了让大家先尝点甜头,这里先给大家列举一些Clojure的主要特性。

Clojure是运行在JVM上的一种语言

Clojure代码可以使用任何Java类库,反之Clojure库也可以被任何的Java代码使用,而且Clojure代码可以像Java代码一样被打包,然后被部署到任何Java应用可以部署的地方:Web应用服务器、桌面应用Swing、SWT以及命令行等。这同时也表明,Clojure所使用的运行时就是Java的运行时——世界上最高效、运转最稳定的平台之一。

Clojure是Lisp

跟Java、Python、Ruby、C++以及其他编程语言不一样,Clojure是Lisp 方言之一。但是,要学习Clojure的话,你要把你所知道的有关Lisp的一切都忘掉:Clojure保留了Lisp最好的那些特性,同时也去掉了其他Lisp方言的那些缺点。同时,作为Lisp的方言,Clojure里面有宏,这是一种进行元编程以及语法扩展的强大工具,几十年来一直是衡量其他类似系统的基准。

Clojure是一种函数式编程语言

Clojure鼓励使用作为“头等公民”的高阶函数,并且提供了一些高效的、不可变的数据结构。选择设计成一门函数式语言使我们可以避免由于对状态的不加控制的修改而导致的一些常见bug,并且也使Clojure成为一种进行并行、并发编程的完美语言。

Clojure提供了进行并行、并发编程的创新式解决方案

现在多核、多CPU以及分布式计算已经逐步成为趋势,使得我们迫切需要一种在设计的时候就把并发编程的思想融入进去的语言和库。Clojure的引用类型强制我们把对象的状态和对象的标识区分开。Clojure对于多线程编程的支持使得我们不用手动加锁、解锁也能编写多线程的代码,就如同Java的自动垃圾回收功能使我们不用显式地请求、释放内存一样。

Clojure是一种动态编程语言

Clojure是动态的,同时也是强类型的(和Python、Ruby类似), Clojure里面的函数调用会被编译(编译速度很快)成Java的方法调用。Clojure另一种意义上的动态是指它支持在运行时更新现有代码或者加载新的代码,加载的代码可以在本地,也可以在远端。这对于交互式的开发以及调试,甚至在不停机的情况下给远端应用打补丁都很有用。

当然,我们并不指望你可以一下子明白上面说的这些优点,但是希望这些优点对你有些吸引力。如果是这样的话,继续读吧。读完这一章,你就可以写出简单的Clojure代码了,并且应该可以理解并且运用Clojure去挖掘你的潜能!获取Clojure

要跟我们一起练习本章中提到的代码的话,你需要两样东西:

1. Java运行时(JRE)。你可以在Oracle的官方网站上免费下载JRE的Windows以及Linux版本(http://java.com/en/download/);而对于Mac OS X来说,JRE是系统自带的。Clojure需要JRE 1.5或以上版本,而使用最新发布的1.6或1.7版本则更好。

2. Clojure本身,可以在clojure.org上面下载(http://clojure.org/downloads)。这本书里面的代码需要Clojure 1.3以上版本才能运行,[1]并且在Clojure 1.4上测试通过。在你下载的zip文件中会找到一个名为clojure-1.4.0.jar的文件,这就是你所需要的全部文件。

对于各种常用的编程工具IDE,比如Eclipse和Emacs,都有很多[2]Clojure插件;398页“工具集”一节有关于Clojure各种工具的介绍。虽然Clojure自带的命令行REPL对于简单地尝试Clojure已经足够了,但是我们鼓励你使用你最喜欢的编辑器——如果你喜欢的IDE有很好的Clojure 插件支持,否则选一个有很好插件支持的IDE。

如果你不想使用某个特定的IDE的话,你至少应该使用Leiningen,它是最受欢迎的Clojure项目管理工具。它会帮你自动下载Clojure,而且它所带的REPL比Clojure自带的REPL要好很多,而且你将来也会每天都使用Leiningen来管理你的项目。347页的“Leiningen”一节有关于它的详细介绍。

如果你现在还不想下载任何东西的话,可以在线运行本书里面的很多例子,http://tryclj.com上有一个浏览器内的Clojure实现。Clojure REPL

很多语言都有REPL,它通常也被称为解释器:Ruby有irb; Python有它的命令行解释器;Groovy有它的控制台;就连Java也有BeanShell。REPL其实是几个单词的缩写:

1.读入(Read):代码被作为字符串从输入源读入(通常是标准输入,但是如果你是从IDE或者其他非控制台环境里面运行REPL的话,那就不一定了)。

2.求值(Eval):代码被求值,产生一个结果。

3.打印(Print):求值的结果被打印到某个输出设备(通常是标准输出,但是如果你是从IDE或者其他非控制台环境运行REPL的话,那就不一定了)。

4.循环(Loop):控制重新跳回到读入(read)阶段。

Clojure语言也有REPL,但是和其他语言是很不一样的。Clojure的REPL不是单纯的解释器或者是一个受限制的、轻量级的Clojure的子集:你在Clojure REPL中输入的Clojure代码都会被编译成JVM的字节码——跟直接执行一个Clojure源代码文件产生的字节码是一样的。在这两种情况下,“编译”都是在运行时进行的——不需要一个[3]单独的“编译”步骤来触发。实际上,Clojure从来没有被解释执行过。这也就意味着下面两件事情:

1.在REPL中执行的代码是在“全速”运行。也就是说,在REPL中运行代码与把代码写在Clojure源码文件中再运行相比没有任何额外运行时开销。

2.一旦你理解了Clojure REPL的工作原理(特别是它的读入和求值阶段),就从根本上理解了Clojure本身是如何工作的。

知道了上面说的第二点,让我们深入Clojure REPL去看看能不能一窥它的原理。

Clojure最有效的编程方式中大量使用了REPL,跟其他语言的典型开发方式不一样的是:它使我们的开发过程有尽可能多的交互性。很强的交互性可以大大提高工作效率,并且非常有意思——都是Clojure 带来的哦。我们会在第10章详细介绍这一点。示例1-1:从命令行启动Clojure REPL % java -cp clojure-1.4.0.jar clojure.main Clojure 1.4.0 user=>

上面的这个命令启动了一个新的JVM进程,而启动的classpath包含了当前目录中的clojure-1.4.0.jar文件,并且以类clojure.main作为它[4]的主入口。如果你还不知道什么是classpath的话,那么我们在331页的“classpath入门”一节有详细的介绍。就现在来说,你可以把classpath当成Python里面的PYTHONPATH, Ruby里面的$:,或者shell里面的PATH,其实就是一些指定的目录或者文件,而JVM会从这些地方加载类文件和资源文件。

当你看到命令提示符user=>的时候,REPL就已经加载完成了,你可以开始输入Clojure代码了。Clojure REPL的命令行提示符中=>之前的部分(user)是当前命名空间(namespace)的名称。可以把命名空间理解成模块(module)或者包(package);我们会在20页的“命名空间”一节详细介绍。Clojure的REPL会话始终是从默认的user命名空间开始的。

下面让我们来看一些真实的代码吧,一个计算平均数的函数,分别用Java、Ruby和Python来实现。示例1-2:分别用Java、Ruby和Python来求平均数 public static double average (double[] numbers) { double sum = 0; for (int i = 0; i < numbers.length; i++) { sum += numbers[i]; } return sum / numbers.length; } def average (numbers) numbers.inject(:+) / numbers.length end def average (numbers): return sum(numbers) / len(numbers)

下面是Clojure的实现:(defn average ❶ [numbers] ❷ (/ (apply + numbers) (count numbers))) ❸

❶ defn在当前的命名空间里定义了一个名叫average的函数。

❷ 这个average函数接受一个名为numbers的参数。要注意的是,在这里我们没有给参数指定类型,所以可以传给这个函数任何类型的集合或者数组,并且集合或者数组中的数字可以是任何数字类型(long、double、float等),这个函数都可以正常工作。

❸ 这个average函数的函数体通过(apply + numbers)来对它们求[5]和,然后把这个和与数字的个数相除,数字的个数是通过(count numbers)来获取的。相除的结果即为我们要求的平均数。

我们把这个函数的定义代码输入到REPL里面,然后调用这个函数,传给它一个包含数字的vector,下面是结果:user=> (defn average [numbers] (/ (apply + numbers) (count numbers))) #'user/average user=> (average [60 80 100 400]) 160有关REPL交互方式的一些约定从这里开始,为了简化我们的表达,后面对于一段代码的输出结果会用;=前缀来表示,比如:(average [60 80 100 400]) ;= 160而代码输出到stdout的内容我们则用;前缀来表示,比如:(println (average [60 80 100 400])) ; 160 ;= nil上面的160是代码打印的内容,所以用;前缀来表示。而nil是println函数的返回值,所以用;=前缀来表示。在Clojure中,以;开头的语句其实是注释。所以你可以把本书中提供的这些示例代码直接输入到REPL,我们在后面举例子的时候都不会包含命令提示符user=>,这也使得大家在复制粘贴代码的时候更加方便。不!括号真的不会让你瞎了眼

许多没有用过Lisp或者上一次用Lisp还是在大学的程序员对于Lisp的语法很不习惯。典型的原因包括:

1.使用小括号(...)来定义代码范围,而不是更常见的大括号{...}或者do ... end。

2.使用前缀表示法:(+ 1 2),而不是更常见的中缀表示法:1 + 2来表示一个操作。

这些反对的声音通常只是简单地因为他们对Lisp不熟悉。Java(以及C、C++、C#、PHP等)用来划定作用域的花括号看起来不是很好吗?为什么要用一个看起来很奇怪的小括号来划定作用域呢?类似的,我们从小开始都是使用中缀表示法来做数学计算,中缀表示法是多么自然啊!我们为什么要用前缀表示法呢?我们都是习惯的生物,除非是为了了解事物之间的差异才去理解新的事物、新的方式,我们通常总是喜欢我们所熟悉的事物。

对于上面两个质疑,我们的回答都是Clojure不是简单地从其他Lisp实现中把语法搬过来,我们所搬过来的语言特性都非常棒,是值得我们对习惯做细微改变的。从以下两个方面来看:

● 统一使用前缀表示法可以极大地简化Clojure的语法,并且消除了复杂表达式产生二义性的可能。

● 而对于小括号的使用(是Lisp里面列表的表示方法)是Clojure作为一个同像性语言的自然结果。关于什么是同像性我们会在第9页的“同像性”一节详细介绍,它的好处是很多的。同像性使得在其他语言中不可能做的事情,如开发和进行元编程以及领域特定语言(DSL),在Clojure中变成可能。

只要对Clojure稍加熟悉之后,你就会渐渐喜欢上Clojure的语法,因为它会大大降低你读/写代码时所需要思考的时间。比如,在Java里面,<<(位左移)跟&(位与)到底谁先执行,谁后执行?每当程序员读到这种代码的时候,都要停下来想一想,有时还要查一查手册。每当程序员需要停下来,在这些代码的两边加上括号“以防万一”的时候,他的大脑中其实就产生了一个“Page Fault”。并且,每当程序员忘记考虑这个优先级的事情的话,一个潜在的bug就被引入他的代码了。想象一下,如果你有一门语言完全不需要考虑优先级会有多爽吧!Clojure就是这样的一门语言!

你可能会说:但是Clojure中的小括号也太多了吧?

其实在任何合适的地方,Clojure都从其他语言中借用了很多语法——比如Ruby, Clojure从它里面借用了数据字面量(Data Literals)。而其他的Lisp方言则是到处使用小括号,Clojure提供了数据和集合(vector、map、列表、set)的丰富的字面量,还包括记录类型(可以认为是Clojure中的struct)。

如果你数数同等规模的Clojure、Java、Ruby以及Python代码中这些划定作用域的字符个数的话(()、[]、{}以及Ruby的||和end等),你会发现Clojure不会比其他语言多,而由于Clojure语言很简洁,通常会更少。表达式、操作符、语法以及优先级

所有的Clojure代码都是由表达式组成的,每个表达式会求值产生一个值。这跟其他很多语言依赖于大量无值控制语句不一样,比如跟if、for以及continue来命令式地控制程序流程不一样,Clojure中的这些控制性的语句都是有值的表达式,跟其他普通表达式没有本质区别。

我们前面看过Clojure里面几个表达式的例子了:

● 60

● [60 80 100 400]

● (average [60 80 100 400])

● (+ 1 2)

这些表达式都会求值产生一个值。而如何对这些表达式进行求值的规则跟其他语言相比要简单很多:

1.列表(用小括号表示)表示函数调用,列表中的第一个值表示操作符,剩下的值表示参数。我们通常称列表中的第一个元素是函数位置(因为这个位置是我们指定函数名,或者命名被调用函数的符号的位置),整个调用表达式被求值成这个调用的返回值。

2.符号(比如前面看到的average和+)会被求值成它们在当前作用域中的值,这个值可能是一个函数,一个本地绑定,比如average例子里面的numbers,一个Java类,一个宏,一个特殊形式(special form)。我们会在后面介绍特殊形式,现在你只要把它当成函数就好了。

3.所有其他表达式求值成它们的字面量:是什么就是什么,比如1就是数字1, “james”就表示一个字符串。

Lisp里面的列表通常被称为S表达式(s-expression)或者sexprs——符号表达式(symbolic expressions)的简称。正确的并且能够被成功求值的S表达式我们称为形式(form):比如(if condition then else)是一个if形式,[60 80 100 400]是一个vector形式等。并不是所有S表达式都是形式,比如(1 2 3)是一个表示有三个数字的正确的S表达式,但是它没办法被成功地求值。因为这个列表里面的第一个元素是一个数字,不能被调用,因此不是一个形式。

上面的第2点和第3点在其他语言中基本是等价的(虽然我们稍后会看到Clojure的字面量的表达能力更强),但是我们只要稍微看一下在其他语言比如Java、Ruby中方法调用的原理,就可以看出它们语法的复杂性。表1-1:Clojure、Java、Python和Ruby中的函数调用语法比较Clojure表达式对应的Java语法对应的Python对应的语法Ruby语法(not k)!knot knot k或者!ka++、++a、a (inc a)a += 1、a + 1a += 1[6]+= 1、a + 1(/ (+ x y) 2)(x + y) / 2(x + y) / 2(x + y) / 2(instance? java. al instanceof isinstance(al, al.is_a? util.List al)java. util.Listlist)Array(if (not a) (inc b) !a ? b + 1 : b - 1b + 1 if not a !a ? b + 1 : [7](dec b))else b - 1b - 1[8]Math.pow(2, 10)pow(2, 10)2 ** 10(Math/pow 2 10)(.someMethodsomeObj.someObj.someObj.someMethodsomeObj "foo"someMethod("fosomeMethod("("foo", o",foo",otherObj.(.otherMethodotherObj.otherObj.otherMethod(0))otherObj 0))otherMethod(0))otherMethod(0))

我们可以看到表格中充斥着各种语法(下面以Java为例,Python和Ruby也是一样的):

● 我们使用了一些中缀表达式(比如a + 1, al instanceof List),但是任何稍微复杂一点的代码都需要使用大量的小括号来覆盖默认的优先级规则,使得代码执行优先级更加明显。

● 对于一元表达式的使用很随意,你可以使用前缀方式(比如!k和++a),也可以使用后缀表达式(比如a++)。

● 对于静态方法的调用,我们把要调用的静态方法所在的类放在整个表达式的最前面,比如Math.pow(2,10),但是……

● 对于实例方法的调用使用的则是一种在Java中不常见的中缀表达式,把该方法所作用的对象放在了“中间”——就是那个this,它[9]被放在方法名称之后,所有正式参数之前。

形成鲜明对比的是,Clojure的调用表达式遵循一个极其简单的规则:列表里面的第一个值是操作符,其余的都是这个操作符的参数。从来不使用什么中缀表达式或者后缀表达式,也没有什么很复杂的操作符优先级需要背,这种简化使得Clojure的语法非常容易学,非常容易记,同时也使得Clojure的代码非常容易阅读。同像性

Clojure的代码是由Clojure自身的数据结构:原子值(字符串、数字等)和集合的字面量来表示的。这种特征的学名叫做“同像性”,[10]我们一般称为“代码即数据”。这相对于其他语言是极大的简化,同时也使得Clojure语言很适合用来进行元编程——而其他语言则不行。要想理解这里面的道理的话,我们要从一般意义上来谈谈语言和语言的内部表示之间的关系。

还记得REPL的第一个阶段是读入你输入的代码吗?每种语言都提供了一种机制去把字符形式的代码转换成一种可以被编译、求值的内部表示。大多数语言是把表示代码的文本转换成抽象语法树(Abstract Syntax Tree, AST)。这个听起来好像很复杂的东西其实很简单:抽象语法树是一种用来表示文本形式的代码所表达含义的正式模型的数据结构。比如图1-1所示的是一些文本代码和其所对应的抽[11]象语法树。图1-1:文本代码到语法树的转换示例

从文本表示到AST的转换是一门语言设计的核心,语言的表达力如何,语言与它的目标问题领域有多契合,大多数领域相关语言的优点都在这里,如果你设计的一门语言是为了解决特定领域的问题,那么这个领域的专家使用起你的语言将会很简单,并且比使用那些通用语言(Java、C++之类的)更容易表达。

这种方式的缺点是大多数语言没有提供用来控制其抽象语法树的办法。语言的文本表示与它们抽象语法树之间的对应关系完全由语言的实现者定义。这迫使一些聪明的程序员使用一些看上去很“聪明”的办法来提高他所写代码的表达力和他使用该语言所拥有的工具的集合:

● 代码生成。

● 文本宏和预处理器(C和C++程序员用了几十年了)。

● 编译器插件(比如Scala, Java里面的Lombok, Groovy的AST转换以及Haskell的模板)。

这些通常都是非常复杂的——之所以复杂是因为语言的设计者把语言的文本语法当做语言的唯一表示方式,使得语言的内在表示方式跟作者的实现方式强烈相关(虽然这些内在表达方式已经暴露了)。

Clojure和所有的Lisp一样使用了一种不一样的方式:没有定义一种将会被转换成AST的语法,Clojure代码是直接用表示抽象语法树的Clojure数据结构来写的——也就是说,你写下的Clojure代码相当于其他语言里面的抽象语法树。回忆一下图1-1里面提到的requiresRole,再来看看它对应的AST是什么样子的(回忆一下我们前面说的,在Clojure中,列表的第一个元素是函数位置)。

(and requiresRole (userHasRole "MODIFY" assetId))

Clojure使用数据来表示语言代码的特性使得Clojure代码可以很容易地用来编写和转换其他Clojure代码。这是宏(Macro)的基础,Clojure中的元程序编程工具要比C语言中提供的那种宏以及其他的文本预处理器要强劲很多。也是当我们对于语言表达能力以及DSL有强烈需求的时候最终拯救我们的一大利器。我们将在第5章详细学习Clojure中的宏(Macro)。

从实用的角度来看,代码和数据之间这种直接的对应关系也意味着你在REPL里面写的代码根本就不是简单的文本代码:你在使用Clojure的数据结构在编程。回想一下示例-2里提到的average函数:

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

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载