Clojure 是 JVM 上的LISP,由Rich Hickey创建。过去一年中,它之所以受到广泛关注,最主要的原因是其并发特性,如支持软件事务存储(Software Transactional Memory——STM)及其他强大的数据结构。所以最近函数式语言颇吸引眼球,这也是很正常的。在 Clojure 1.0 发布几个月之后,用 Clojure 实现的现实项目也终于出现了。
FlightCaster 是一个新站点,用来提供航班延误预告。它的 Web 前台是用 Rails开发的,部署在Heroku上。后台处理数据的程序则是用Clojure开发的,用到了 Hadoop 、 Cascading 、 Cloudera 以及其他工具。
我们就该项目采访了Bradford Cross,了解用于开发 FlightCaster 的架构,如何用 Clojure 实现它,以及 OOP 程序员采用 Clojure 和 Lisp 时应该掌握的使用技巧。
InfoQ:你能解释一下 FlightCaster,也就是其分析部件,都做些什么吗?
Flightcaster 实时预测航班延误时间。其分析工作涉及到统计推断及机器学习技术的应用。关于航班延误预测的精确技术并不存在,即使其存在,我也没什么可说的。 :-)
InfoQ:你们的架构是什么?你们在 Clojure 群中发表的文章中,提到了 Hadoop 之上的一层 Cloudera ?它们是怎么组织在一起的?
Cloudera 非常强大,为需要使用 Hadoop 开发大规模分布式处理的人们提供服务、Hadoop 分发、部署脚本及 AMI(Amazon Machine Instances)。我们使用 Cloudera 将 Hadoop 集群部署在 EC2 上,用于进行数据处理和分析工作,结点数介于 10 到 100 之间。我们发现 使用 Cloudera 分发可以减少把 Hadoop 部署在 EC2 上的复杂性。我们只有两个人从事研究方面的工作,因此采用 Cloudera 对我们帮助很大。
InfoQ:FlightCaster 的哪一部分是用 Clojure 写的?
基础设施的另一个关键部件是 Cascading;在 Hadoop 上非常棒的一层,其增加了附加抽象概念和功能。我们向那些正在用 Hadoop 进行大量数据处理和挖掘的人强烈推荐 Casading。 我们所有的 Clojure 都是运行在 Cascading 上的。
该系统里有两个主要部分是用 Clojure 编写的。 一是把数据预处理并转换成适当视图用以分析的所有操作。包括过滤、多阶段分布式连接(destributed join)等等。把来自异质数据源的适当视图转变为非结构数据是非常复杂的。例如,我们不得不把时间序列视图构建到数据中,因为我们大量的分析需要考虑时 间因素。任何构建过这类系统的人都知道数据处理的工作量到底有多大,而 Clojure + Cascading 帮了大忙。
二是所有的统计推断和机器学习代码。如果该系统存在这一部分,我们将更深入介绍系统的这一部分。假定该系统存在这一部分,它应该能从 Clojure 优秀的 功能抽象、宏系统、丰富的不可变数据结构和序列处理类库、解构(destructuring)、组成复杂多阶段计算(中间可能出错)的单体抽象 (monadic abstractions)等诸多特性中获益良多。
InfoQ:你们用到了 Clojure 的并发和 STM 特性吗?
尽管这些特性很酷,但我们没有用 Clojures 构建并发特性。相反,我们利用了 Clojures 的另一个特性,务实的选择将其构建在 JVM 上。我们只是 把并行和分布式计算委托给 Cascading + Hadoop,我们在这两者之上又加了一层,使用起来很友好,如果有时间,我们会将其开源。
InfoQ:你能简要介绍一下你们的 Clojure 代码是如何组织的吗?比如,如何使用命名空间、muti-methods(多重分派)、macros(宏)等等。
过往函数式编程的经验使我们能够将代码组织成非常“函数”的形式。我们使用命名空间的方式与在其它语言中使用命名空间的方式相同。我们尽量少用 macros 和 muti-methods,就算用也只是在适合的地方使用。人们对 Lisp 的所有印象就是其有大量的 macros 和 meta-sauce。 尽管从某些方面来说这是对的,但是用 FP 基础构建块,你能走得更远;如 lambdas、HOFs、currying、partial application 等等。 我不能算是精通 monad,但是我觉得 mondads 和 multi-methods 及 macros 都差不多。这些抽象概念不但功能强大,而且也很酷,吸引 了不少眼球。当这些抽象概念有助于简化开发时,我就会设法使用这些工具。但是这要求思维缜密,使用原始的函数编程方式及数据结构你也能做得很好。
最后谈一下 Clojure 中的 monads,有些人认为 monads 是用于状态的,可是 Clojure 已经有了许多有效方法来处理状态,为什么还要用 monads。我们没有使用过 state monad,但是用过许多其他很有用的 monads。我很欣赏 Brian Beckman 对 monads 的描述:他们只是伪装的函数合成物而已。使用这一概念的最大好处是,能够安全构造出中间可能失败或碰到空值的多阶段计算。
虽然 Destructuring bind 看起来并不像 macros 和 monads 那样引人注目,但在实践中它实际上是一个非常强大的抽象概念。Rich 选择从模式匹配中解耦 destructuring bind 的方法非常聪明。我相信很快在 Clojure 里就会有 ML 风格的模式匹配,这一切正在实现中。
InfoQ:你提到过用于将数据格式输入到 Clojure 数据结构的 Clojure reader:你在使用 Reader macros 吗?
Clojure 没有 reader macros,我们也压根不需要。要读写 Clojure 数据结构,我们只需使用 print-dup 语句即可,它可以让我们定义 multimethods 来 分派 printers。只有在你需要读写没有内建的类型时,才真正需要实现 Printer。例如,我们有一个针对 joda-time 日期的特殊 printer,那么我们也将以一种特殊的方式将他们读回。
InfoQ:你提到过编写 Clojure 数据结构——你是用其来序列化传输、存储或其它用途的数据吗?
我们将 Clojure 数据结构用作通信和存储的中间表示。例如,我们所有数据转换工作的输出都是 Clojure 数据结构形式,这种中间表示遍布于我们所有 的 Hadoop 工作中。这是我们置于 Cascading 和 Haooop 之上的那一层东西的关键,可以让我们免于处理 Hadoop 输入格式。
InfoQ:你们有什么想加到 Clojure 或 Clojure 生态系统中的东西么(类库、工具……)?
有人提议加个高质量的 destructuring 模式匹配工具,这个东西挺有用。但我对局部改进更感兴趣。我们没有用模式匹配,而且 Clojure 有许多很好的抽象概念,因而并不会缺失太多模式匹配,但是我认为它还是让很多地方的代码更加整洁了。 如果 Rich 开放该 reader,我想一定很酷,或许这将帮助为模式匹配和 monad 实现创建良好的语法。其次,Clojure 是我第一次使用 Lisp,因此我并没有使用 reader macros 的经验,因此,在这方面我并不在行。
如果 Rich 不保留竖线也挺好,这样我们可以将它作为我们核心 DSL 的一部分,用来作为条件概率记号。 :-)
InfoQ:对于你所使用的 Clojure 类库,你有什么建议?
使用 Clojure 的第一个建议是:Clojure-core 和 Clojure-contrib 都很小,因此最好通读全部代码。从中你将发现非常好的东西。留意所有神奇的数据结构及数据结构处理函数。例 如,Clojure 有一个友好的 Set 及 Set 操作实现,以及一些准关系代数实现。 我把 Clojure 看作是一种面向函数编程的数据结构。
Clojure 拥有一套神奇的数据结构。此外,所有这些数据结构都有书面陈述,因此 reader 和 destructuring 都很自然。所有这些结合在一起让人非常愉快。
传承自 ML 的函数语言中,函数是类型签名。而在 Clojure 中,函数是数据结构拓扑签名。
InfoQ:FlightCaster 的 Web 前台是用 Rails 编写的,部署在 Heroku 上。为什么选择 Rails 和 Heroku?
当我开始介入 FlightCaster 开发时,已经定下来用 Heroku 和 Rails 了。这一决定是有道理的:Ruby 和 Rails 生态环境生产效率比较 高,并且为构建 WebApp 铺平了道路。Heroku 和 Rails 对团队来说是很自然的选择,因为创始人中有两个具有 Rails 开发经验,Herkou 的 创始人之一还是我们 CEO 的密友。:-) Flightcaster 和 Heroku 共处一室,这太棒了!拥有这种内部联系有益无害。
InfoQ:你们是如何将 Web UI 与 Clojure 后台进行集成的?
Rails 不只是 Web 前台,而且还是 WebServer。我们使用 Clojure 来进行数据处理和机器学习研究。我们使用一种非常简单的策略来实现集成:我们让 Clojure 代码产生预告模型的 json 中间表示,然后把它推向 Ruby 端,以 json 格式来读取这些数据。
Clojure 第一本书的作者——Stuart Halloway ,最近发表了一篇文章介绍了使用Clojure 的不同技巧。该文章提供了Clojure 中的封装、多态等一些例子——那些拥有OOP 背景的开发者会对此产生兴趣(这是类和继承之外的生活)。
要了解更多Clojure 方面的信息,请参见InfoQ 的 interview with Rich Hickey ,其中谈及了 STM、并发性及 multimethod。 Rich 关于 Clojure 的视频讲解提供了有关 Clojure 特性及其设计原则的更多详细内容。
查看英文原文: Clojure and Rails - the Secret Sauce Behind FlightCaster 。
感谢刘申对本文的审校。
给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家加入到 InfoQ 中文站用户讨论组中与我们的编辑和其他读者朋友交流。
评论