FlightCaster 秘笈——Clojure 和 Rails

阅读数:3497 2009 年 11 月 16 日

Clojure是 JVM 上的LISP,由Rich Hickey创建。过去一年中,它之所以受到广泛关注,最主要的原因是其并发特性,如支持软件事务存储(Software Transactional Memory——STM)及其他强大的数据结构。所以最近函数式语言颇吸引眼球,这也是很正常的。在 Clojure 1.0 发布几个月之后,用 Clojure 实现的现实项目也终于出现了。

FlightCaster是一个新站点,用来提供航班延误预告。它的 Web 前台是用 Rails开发的,部署在Heroku上。后台处理数据的程序则是用Clojure开发的,用到了HadoopCascadingCloudera以及其他工具。

我们就该项目采访了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 中文站用户讨论组中与我们的编辑和其他读者朋友交流。

收藏

评论

微博

发表评论

注册/登录 InfoQ 发表评论