R和Ruby数据分析之旅

978-7-115-30370-7
作者: 【新加坡】Sau Sheong Chang
译者: 钱昊刘熙
编辑: 杨海玲

图书目录:

详情

本书旨在通过6个现实生活的实例,让读者通过使用一些基本的数学知识和简单的Ruby和R结构,学会如何对问题建模,并找到解决方案。先是建模和模拟环境和系统,然后观察和分析结果,通过编程的之眼来看周围的世界。

图书摘要

版权信息

书名:R和Ruby数据分析之旅

ISBN:978-7-115-30370-7

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。


著    [新加坡] Sau Sheong Chang

译    钱 昊  刘 熙

责任编辑 杨海玲

人民邮电出版社出版发行  北京市丰台区成寿寺路11号

邮编 100164  电子邮件 315@ptpress.com.cn

网址 http://www.ptpress.com.cn

读者服务热线:(010)81055410

反盗版热线:(010)81055315


Copyright ©2012 by O’Reilly Media, Inc.

Simplified Chinese Edition, jointly published by O’Reilly Media, Inc. and Posts & Telecom Press, 2013. Authorized translation of the English edition, 2013 O’Reilly Media, Inc., the owner of all rights to publish and sell the same.

All rights reserved including the rights of reproduction in whole or in part in any form.

本书中文简体版由O’Reilly Media, Inc.授权人民邮电出版社出版。未经出版者书面许可,对本书的任何部分不得以任何方式复制或抄袭。

版权所有,侵权必究。


这是一本十分特别、充满趣味和奇思妙想的编程及数据分析的书籍。在作者的带领下,读者将学会使用Ruby和R这两个强有力的编程工具,对现实生活中的一些问题进行探索,尝试发现一些事物运行的模式、规律,通过数据的获取、建模和分析找出解决方案。

本书开篇分别对Ruby和R语言做了从零开始、简明扼要的介绍,包括安装、开发环境的使用、基本语法和重要特性。这为后面的探究工作打下坚实的基础。

探索之旅包括6个部分,其中包括处理办公室的卫生间安排问题,考查简单的市场经济体系的运行状况,挖掘特定用户的电子邮件行为模式,用音频和视频两种不同方法来测定自己的心率,模拟鸟儿结群飞行的场景,最后通过人工营造一个具备进食、生殖和进化特性的种群,来看其在现实世界中的一些影子。

阅读本书,读者不但能学到Ruby和R的基本知识和技巧,更能学会如何借助编程来对现实生活中的问题进行建模和模拟,特别是怎样对数据进行集成、处理和分析。相信读过本书之后,读者将有能力和意愿去开启自己的探索和发现之旅。


O’Reilly Media通过图书、杂志、在线服务、调查研究和会议等方式传播创新知识。自1978年开始,O’Reilly一直都是前沿发展的见证者和推动者。超级极客们正在开创着未来,而我们关注真正重要的技术趋势——通过放大那些“细微的信号”来刺激社会对新科技的应用。作为技术社区中活跃的参与者,O’Reilly的发展充满了对创新的倡导、创造和发扬光大。

O’Reilly为软件开发人员带来革命性的“动物书”;创建第一个商业网站(GNN);组织了影响深远的开放源代码峰会,以至于开源软件运动以此命名;创立了《Make》杂志,从而成为DIY革命的主要先锋;公司一如既往地通过多种形式缔结信息与人的纽带。O’Reilly的会议和峰会集聚了众多超级极客和高瞻远瞩的商业领袖,共同描绘出开创新产业的革命性思想。作为技术人士获取信息的选择,O’Reilly现在还将先锋专家的知识传递给普通的计算机用户。无论是通过书籍出版、在线服务或者面授课程,每一项O’Reilly的产品都反映了公司不可动摇的理念——信息是激发创新的力量。

“O’Reilly Radar博客有口皆碑。”

      ——Wired

“O’Reilly凭借一系列(真希望当初我也想到了)非凡想法建立了数百万美元的业务。”

      ——Business 2.0

“O’Reilly Conference是聚集关键思想领袖的绝对典范。”

      ——CRN

“一本O’Reilly的书就代表一个有用、有前途、需要学习的主题。”

      ——Irish Times

“Tim是位特立独行的商人,他不光放眼于最长远、最广阔的视野并且切实地按照Yogi Berra的建议去做了:‘如果你在路上遇到岔路口,走小路(岔路)。’回顾过去Tim似乎每一次都选择了小路,而且有几次都是一闪即逝的机会,尽管大路也不错。”

      ——Linux Journal


我曾经涉猎过数以百计的程序设计书籍,从Programming Ruby这样的语言经典,到《代码大全》这样的风格指南。但在接触本书之前,从未想到过编程书可以像这样别具一格、妙趣横生。我也曾做过多种不同类型的程序设计工作,从编程竞赛中的算法题,到大型的Web项目,抑或是电子游戏。但在接触本书之前,从未想到过程序设计可以像书中这样,用来对我们日常生活的方方面面进行探索。

当你遇到一层办公楼中卫生间的安排问题时,你可能想到用数学建模方法去解决。当你想要验证亚当•斯密的市场经济运行规律是否有效时,你可能会想到通过理论和事实进行推演。当你想要测定自己的心跳时,你可能会用手表加数数的方法,或是求助于精密的电子仪器。但你是否想过,这些问题可以通过简单的编程工作,得到相当有效的解决?本书的作者将带领你借助Ruby和R这两种流行的、强大的(而且是免费的)编程语言,开启一段激动人心的探索之旅,寻求诸如前述的、不同领域问题的答案,并发现其中的规律和模式。在阅读和实践书中所讲的内容时,你不仅仅是一名程序员,更像是一位探险家(作者本人在引言中也如是说),向看似平淡无奇的,却充满未知的世界发起挑战!

作为读者,你不需要有过任何的编程经验,作者将带你从头开始学习Ruby和R语言的基本知识和特性。尽管由于篇幅所限,本书不能涵盖两种语言的一切内容(要完成这一任务,本书篇幅再翻几倍也不见得够),但已经足够你了解它们的精华所在。而在后面的探索之旅中,你更会学到从发现问题、建模、模拟,到收集和处理数据、分析结果、得出结论这一整套的研究事物的方法。你将学会用不同的视角来看待世界,并发现其中的规律和乐趣,成为一名名副其实的探索者。

非常高兴能与我的好朋友刘熙合作,承担本书的翻译工作。特别是有幸将这样一本精彩的书籍作为我的首本译著,实在是激动人心。这里要对原作者Sau Sheong Chang博士表达深深的敬意,并对给予我们这一美妙工作机会的人民邮电出版社,特别是责任编辑杨海玲女士,表示诚挚的感谢。

由于水平所限,译文中如有疏漏及不足之处,还请不吝批评指正。欢迎随时联系我们,我的邮箱:MerryMoney88@gmail.com。

真的很怀念对照着iPad上的电子版原著,在我相伴多年的HP(恰好是作者就职的公司)笔记本上一句句敲打和改订译文的那些日日夜夜。

钱昊

2012年11月8日于海淀朱房


钱昊 毕业于北京大学计算机系,从小热爱程序设计,熟悉多种编程语言,曾有过使用Ruby进行大型Web项目开发的经验,并对翻译工作有着浓厚的兴趣。

刘熙 在北京大学元培学院完成本科学习后,又进入北京大学工学院攻读硕士学位,同时在微软Bing部门工作,主攻数据挖掘方向,已有过两本译著。

两人从小学奥数班就相识,一路同学到中学、大学,志趣相投。本书的翻译堪称两人一次重要而难忘的合作经历。


Sau Sheong Chang从事软件开发工作已超过17年,至今仍是一个激情四射的程序员。他主要开发网络应用程序,近来则涉足数据相关的和云相关的系统。他在Ruby编程领域已经活跃了6年,使用R语言也超过了1年。他在当地的开发者社区非常活跃,且在许多技术会议上发表过演讲,尤其是Ruby领域的会议。

他已经出版过两本书,一本是2008年的Ruby on Rails Mashup Project,另一本是2010年的Cloning Internet Applications with Ruby,两本书都由Packt Publishing出版。Sau Sheong Chang是新加坡惠普实验室的应用研究主管,专注于云计算、大规模数据和城市化。他以前曾在一家知名的网络游戏公司做过一段时间的首席技术官,也曾在Yahoo!东南亚做过一段工程主管。更多详细信息可以在www.saush.com/profile上找到。


本书封面上的动物是冠海豹,这是一种生活在北大西洋的有鳍哺乳动物。它的学名Cystophora cristata在希腊语中是“气囊携带者”的意思,这是因其4岁左右时会在雄性头上长出可充气膨胀的冠子而得名。这冠子被用于求偶,也可以用来威胁竞争对手或敌人——冠海豹是所有海豹中最具侵略性的种类之一。当处于未充气状态时,气囊会从前额垂到两眼之间。雄性冠海豹在一个鼻孔里还有第二个气囊,可以通过关闭另一个鼻孔的瓣膜来充气。

与雄性相比,雌性冠海豹外貌不那么有特色,个头也较小:它们差不多有2.1m长,200kg重,雄性则平均长2.4m,重约300kg。然而,雌雄两性都拥有银色的皮毛,其中夹杂着不规则的暗斑。与其他半水生的哺乳动物一样,它们拥有光滑的带爪的蹼,可以有效地在水中移动,而在陆地上就比较笨拙了。

冠海豹经常迁徙,它们会用整年的时间,独自穿越相当长的距离,以寻找食物。仅在深冬和夏季会回到繁殖地,前者是为了交配,后者则为了换毛。它们在水中猎食,能够下潜到61m深,并在水下停留将近1小时。它们的日常食谱根据所在地不同而变化,但通常包括鱼类、乌贼、虾类、章鱼和蚌类。

在出生后的前14个月里,冠海豹幼仔皮毛是蓝灰色,腹部则是苍白色,这使它们获得了“蓝背”的昵称。它们的哺乳期平均仅4天,在哺乳动物中是最短的。然而,在此期间,幼仔的体重几乎会增加一倍,每天增重达4.5~6.8kg——它们的母乳的脂肪比例高达60%~70%。 封面图片源自Riverside Natural History一书。


要用一些大无畏的探险家——比如斐迪南·麦哲伦(Ferdinand Magellan)、詹姆斯·库克(James Cook)、罗尔德·阿蒙森(Roald Amundsen)——和我这样的人比较,是一件挺困难的事。这些探险家乘风破浪,披荆斩棘,直面未知的险境,去发现新的世界(至少对他们当时所处的文明来讲算“新”)。而迄今为止,我运动方面最大的成就大概就是完成了10公里的慈善短程马拉松——而且是用走的。

过去的那些探索者在发现了未知的地区、宣示所有权的时候,可真是风光。克里斯托弗·哥伦布(Christopher Columbus)只需要从欧洲一直向西航行,就能发现两块完整的大陆。可我们今天的选择就少多了。地球上已经没有多少未被发现的大陆板块了,即使是海洋中最深的地方——马里亚纳海沟,也已经被人征服了。

但是,我的确也是一位探险家,而且将要阅读这本书的你也是。尽管已知的现实世界已经被人征服得差不多了(见图0-1),未知的世界却依然傲视着我们。

图0-1 去往南极点的斯科特远征队(照片来自Public Domain Review: http://publicdomainreview. org/2012/03/29/remembering-scott)

我们大家生来都具有一种为这身边的世界感到吃惊而诧异的感觉。但是,随着我们渐渐成长,渐渐厌倦,很多人失去了这种感觉。我认为,这其中的部分原因是,我们其实对周遭的世界理解得还不够,因此也就漠不关心。按一下遥控器,电视机就开了——这是怎么做到的呢?当我们第一次提这样的问题时,很可能会招来白眼,或是闪一边儿去的手势。只要能看到下一季的《美国偶像》,谁在乎这种问题呢?于是别人的这种反应也就很快变成了我们自己的反应。

那么,在这本书里,我将带你走过蜿蜒的路途,找回那个原始而本真的你。我们会再次发现魔力的存在。在全书结束的时候,很有可能你将从我们停下的地方继续进发,继续探索你自己的征程。

在我们生命的每一分每一秒,我们都深陷于数据的泥沼之中。我这话并不是隐喻,也绝非在单纯地夸大大数据的存在。

其实,我们周遭的数据极其之多,我们的眼睛已经进化得能够在每毫秒这么短的时间内切断一会儿与环境的联系。在一种称作“扫视掩蔽”的现象中,随着眼球的快速转动(即扫视),大脑会暂停一会儿,以消除投射到视网膜上的模糊图像。模糊图像的用处不太大,所以大脑就丢弃它们,使我们在扫视时有效地失明(我们自己意识不到)。

我们今天处理数据的方式和这种扫视掩蔽效应有很多相似之处。数据来得实在太快太频繁,我们常常将它们屏蔽掉。周围有很多数据可以供我们提取并分析,找出问题的答案,但问题就在于,应该怎么做到。

在(遥远的)过去,拥有利用数据与学识来破解秘密的技巧的总是那些天才,只有运气好的极少数人才有机会偶然发现答案。现在再也不是这样了。尽管智力仍然是一个先决条件,但由于计算机和程序设计的出现,如今,在提炼信息的道路上,我们可以从无聊、烦琐、重复性的数据处理中脱离出来了。

只是,我们还并没有做到。

无论如何,至少对于大多数人来说,的确没有。只有科学家和数学家们例外,他们早就紧紧掌握了能使他们更高效地工作的工具。如果你是来自上述这两大人群,那你可能已经充分利用了计算机的威力。

可是,对于程序员和其他很多人来说,他们最开始编写计算机程序只不过是为了给业务提供工具,或者是辅助改善业务流程。所有这些,都是为了利用计算机来降低成本,增加收入,提高效率。在很多职业程序员看来,编程只是个差使,枯燥乏味,不用动脑,养家糊口而已。我们其实已经忘记,在探索发现中,计算机的前景多么广阔,程序设计的威力多么强大。

这本书正是为了要试图找回发现的美妙和神奇。我希望这本书能够揭示一些你之前不知道或不了解的事情。我还希望它能够帮助你,通过我们每天触手可及的世界去发现新大陆。最后,我更希望,你能够通过它获取探索的能力,用程序设计和数据分析去发现新鲜事物。

尽管在这本书里,我们所要探索的仍是现实世界,但更多情况下并不是这样。要在位和字节之间探索整个世界有点困难。因此,如果我们无法探索我们所生存的这个世界,那我们就要尝试其他的——换言之,我们要使用模拟(simulation)。

模拟是用来探索我们无法控制的事物的一种好办法。我们每时每刻都在做着模拟。在我们小的时候,我们自己总是创造出假想的世界并且活在其中。借由此,我们也能够更好地去理解现实世界。我们至今仍然在做着模拟,通过神奇的电视(尤其是电视连续剧)和电影——我们会把荧幕上的角色视作真实的。且不论是好是坏,至少像电视机这样的模拟手段的确影响了我们的现实生活,甚至还会影响我们的梦境。例如,由美国心理学会所开展的一项调查显示,60多岁的人中(在彩色电视机尚未流行的年代成长起来),约20%会回想起他们做过的梦是鲜亮生动的。而30岁以下的人中,有80%确信他们的梦境是彩色的[1]

在这本书里,我们会通过模拟来创建实验,分离变量,并提出假设来解释实验结果。对于我所描述的实验,或者我所提出的假设,你可能同意,也可能反对,但这些都不重要。通过我们共同走过的这段旅程,我真正希望你获得的,是能认识到,编程所能够解决的远远不止商业活动而已。我希望能够达到的是,你最终可以设计出你自己的实验,实现它们,并发现你自己的新世界。

我们将通过程序设计和数据分析来进行这场宏伟的探险。那么,你需要准备些什么呢?当然要准备些工具了,这也正是接下来两章的主题。可用的工具并不仅有这些,但在这本书中我们只用这些。

我们要用到的这两种工具就是Ruby和R。我之所以选择它们,是有着特定的目的的。一方面,Ruby很好学,也很好读,特别适合用易读的代码来解释一些概念。我会用Ruby来进行模拟和预处理,得到数据。另一方面,R则见长于分析数据,以及生成可视化的图表。

尽管要欣赏这本书的话,你并不需要一定会使用Ruby和R进行编程,但我会假定你具备一些对编程的基本理解。具体地说,我会假定你修过计算机科学或相关方面的课程,或者是使用过任何一种编程语言进行过一些简单的程序设计。

在这两章之后,其他每一章都或多或少是相对独立的。每一章都会就一个思想开展探索,从对现存问题的理解开始,接下来尝试去回答它,通过模拟或一些处理来获取数据,然后就要分析数据,并基于分析得出一些结论。

要探索的思想来自于不同的领域,从经济学到进化论,从医疗保健到工作环境设计(具体场景是找出办公室卫生间的合理数量)。某些思想显得很宏大,而某些思想则非常私人化。之所以选择如此多元的思想,正是为了展示,对探索可能性的限制其实只来自于我们自己创造力的贫乏。

每一章在一开始的时候都显得规模不大,但我们会逐层地增加复杂性,以将其核心的思想具体化。实验会围绕着基本思想进行,而最终所得出的假设、结论和结果都具有一定的偶然性。比如,对于我的结论和对结果的阐释,你可能同意或者反对。但就这本书而言,探索的过程要比结果更为重要。

一切就绪,让我们就此出发!祝愿你愉快地体验接下来的两章,并享受之后的探索——勇敢地探索吧!

本书使用如下排版约定。

 

 

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

 

 

 

 

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

 

本书的目标是帮助你完成工作。一般而言,你可以在自己的程序和文档中使用本书中的代码,如果你要复制的不是核心代码,则无须取得我们的许可。例如,你可以在程序中使用本书中的多个代码块,无须获取我们许可。但是,要销售或分发来源于O’Reilly图书中的示例的光盘则需要取得我们的许可。通过引用本书中的示例代码来回答问题时,不需要事先获得我们的许可。但是,如果你的产品文档中融合了本书中的大量示例代码,则需要取得我们的许可。

在引用本书中的代码示例时,如果能列出本书的属性信息是最好不过了。属性信息通常包括书名、作者、出版社和ISBN。例如:“Exploring Everyday Things with R and Ruby by Sau Sheong Chang (O’Reilly). Copyright 2012 Sau Sheong Chang, 978-1-449-31515-3.”

在使用书中的代码时,如果不确定是否属于合理使用,或是否超出了我们的许可,请通过permissions@oreilly.com与我们联系。

如果你想就本书发表评论或有任何疑问,敬请联系出版社。

美国:

O’Reilly Media Inc.

1005 Gravenstein Highway North

Sebastopol, CA 95472

中国:

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

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

我们还为本书建立了一个网页,其中包含了勘误表、示例和其他额外的信息。你可以通过如下地址访问该网页:

http://oreil.ly/everyday-things-r-ruby

关于本书的技术性问题或建议,请发邮件到:

bookquestions@oreilly.com

欢迎登录我们的网站(http://www.oreilly.com),查看更多我们的书籍、课程、会议和最新动态等信息。

我们的其他联系方式如下。

Facebook:http://facebook.com/oreilly

Twitter:http://twitter.com/oreillymedia

YouTube:http://www.youtube.com/oreillymedia

在这部分里,我要感谢所有帮助过我的人,正是因为有了他们的帮助才使我能够创作出你现在手里捧着的这本书。多年来我了解到,写书绝不是一项仅靠作者就能够完成的工作,而需要作者、专业的团队、一批审阅人和支持者共同协作。我想感谢以下所有人,排名不分先后。

最后,我想把这本书献给我的家人,无论我做什么,他们都是我的灵感和动力之源。谢谢我的爱妻Wooi Ying再一次(已经是第三次了)地耐心对待我的工作,感谢她理解我为什么想要搞懂所有事物。谢谢我将满十岁的儿子Kai Wen,我希望这本书能够激励你成为一名眼界开阔的探险家,正如我自己的一生一样。

[1]  Okada, Hitoshi, Kazuo Mastsuoka, and Takao Hatakeyama, “Life Span Differences in Color Dreaming”, Dreaming 21, no 3.(2011), 213-220.


《夺宝奇兵》一直是我最喜欢的系列电影。在我年少时,哈里森·福特就是我心中的英雄。我一直很喜欢印第安纳·琼斯抽鞭子的样子。其实正是在《夺宝奇兵》里,我第一次知道鞭子是什么东西。

《夺宝奇兵》最早的两部——《法柜奇兵》和《魔域奇兵》中,印第安纳正值壮年,坚定刚毅,脾气暴躁。在我看过这两部之后,心里就对他标志性的帽子和鞭子产生了疑惑——为什么一定要戴那样一顶毡帽,为什么居然要拿一条鞭子?

最后,所有的答案都在第三部《圣战奇兵》中揭晓了。这部电影告诉了我们印第安纳的身世,解释了帽子和鞭子的事情,还告诉了我们他为什么要做所有这一切。那可真是惊喜的一刻——虽说在大背景下其实显得并不那么重要。

那么,这跟一本编程书有何干系呢?正像印第安纳离不开帽子和鞭子一样,我们这本书也将主要使用两种工具——Ruby和R。帽子和鞭子作为野外考察的工具,对考古学教授来说不同寻常,同样,在探索周围世界方面,Ruby和R也并不寻常。它们能够让事情变得更有意思。

这两种工具都各需一章专门介绍。我们首先来介绍Ruby,下一章再介绍R。显然,我肯定无法在书里仅用一章就把Ruby编程语言完完整整地解释一遍。所以,我打算提供足够的信息供你开开胃,希望能够吸引你去阅读更深入探讨Ruby的好书。

你首先可能就会问(除非你已经对Ruby充满热情,已经知道为什么了,要是这样的话,你接下来点点头就可以了),为什么在这本书里我要选择Ruby作为一种主要工具。我有很多很好的理由。不过具体到我们这本书的目标上,我想集中说以下两点。

第一,Ruby是一款非常适合人类的编程语言。Ruby的创造者松本行弘经常说,他试图使Ruby很自然,而非很简单,让它就像能够反映我们的生活一样。用Ruby编程很像是在和你的好朋友——计算机交谈。Ruby的设计本意就是让编程更有意思,让人们集中精力在编程中埋头苦干。例如,要在屏幕上显示“I love Ruby”(我爱Ruby)10遍,只需要告诉计算机:

10.times do
  puts "I love Ruby"
end

如果你对C以及它的同族语言(比如Java)很熟悉,可能已经知道,要检查某个变量a_statement是否为真,你需要做这样的事情(注意,在C中需要使用整数1来代替true,因为C中没有布尔类型):

a_statement = true;
if (a_statement == true) {
  do_something();
}

而你在Ruby中当然也能做同样的事情,你可以这样做:

do_something if a_statement

这样的代码非常容易阅读,因此也就很容易维护。尽管Ruby有时也会很深奥,但通常情况下,它都是一种使他人易于阅读和理解的编程语言。你也可以想象到,对于本书,这将是一种非常有用的特征。

第二,Ruby是一种动态的语言。对于作为读者的你来说,这一点也就意味着,你可以从本书中复制代码,扔到文件(或者是你后面要看到的交互式Ruby shell)里,然后直接运行。不需要对makefile进行乱七八糟的设置,不需要得到库的正确路径,也不需要在运行示例之前先编译编译器。剪切,粘贴,运行——这就是需要做的一切。

这就是我在这本书里使用Ruby的两个主要原因。如果你很想了解为什么许多其他编程语言的程序员也开始转向Ruby,你可以访问Ruby的网站(http://www.ruby-lang.org),或者在网上搜索,你将会看到很多人对Ruby交口称赞。

在使用Ruby之前,我们当然要首先把它安装到机器上。这是个很简单的准备活动。怎样安装主要有三种选择,选择哪种取决于你的热情有多么高涨。

1.从源代码安装

如果你雄心勃勃,可以尝试编译Ruby。这意味着你的平台上需要有能够编译Ruby的工具。所以,除非你确实想要严肃对待Ruby,否则我还是建议你安装已编译的:利用第三方工具,或利用你的平台上管理软件包的工具。

要通过源代码编译Ruby的话,可以访问http://www.ruby-lang.org/en/downloads下载源代码,然后用你平台上的编译器进行编译。在这个网站上,你还可以获取更多的信息。

2.利用第三方工具安装

或者,你也可以选择那些很流行的第三方工具。我推荐你使用最为流行的工具,在OS X或Linux上是Ruby版本管理器(Ruby Version Manager),在Windows上是RubyInstaller。

Ruby版本管理器(RVM)可能是非Windows平台上最为流行的第三方工具了。使用RVM有一个鲜明的优点,就是能够安装各种版本的Ruby,并且能够很容易地在各个版本之间切换。安装RVM虽然不是非常困难,但也并非一蹴而就。至少在今天来说,安装RVM要这样进行。

首先,你必须安装Git和curl。然后,在命令行中输入以下命令:

$ curl -L get.rvm.io | bash -s stable

然后,用下述命令重新载入shell(或者是类似的其他命令,这取决于你的shell):

$ source ~/.profile

这时你就可以运行rvm了。接下来要做的是,检查是否已经具备了安装Ruby的所有条件:

$ rvm requirements

一旦准备齐全,敲入rvm命令来安装你想要的Ruby版本。我们的示例中将安装Ruby 1.9.3:

$ rvm install 1.9.3

这之后,检查你想要的Ruby版本是否已经正确安装:

$ rvm list

你可以看到一系列(或者至少应该有一个)安装好的RVM Ruby。如果这是你第一次安装,那应该还没有默认的Ruby,所以你需要按下列命令设置一个:

$ rvm alias create default ruby_version

将上面的ruby_version替换成你刚刚安装上的版本(例如ruby 1.9.3p125)。大功告成啦!如果你在安装过程中受阻,可以查看RVM网站http://rvm.io/上的安装指导。

如果你使用的是Windows,那么就无法安装RVM。这种情况下,你可以创建一个虚拟机,安装上你最喜欢的GNU/Linux版本,然后再进行;或者只需使用很简单的RubyInstaller。访问http://rubyinstaller.org/downloads,下载正确的版本,然后安装。RubyInstaller包含了很多基于C的自然扩展,这是一个额外的好处。这是一款图形化的安装工具,所以很容易就可以快速完成配置。

3.使用平台上的软件包管理工具安装Ruby

如果以上方法都不适合你,你可以选择使用自己系统里的软件包管理工具,对于Debian系统(也包括Ubuntu),你可以使用下面的命令:

$ sudo apt-get install ruby1.9.1

这个命令能够安装Ruby 1.9.2。的确有点不可思议。

对于Mac机器而言,Ruby已经包含在OS X之中了,但通常是个较老的版本(Lion包含了Ruby 1.8.7,更早的系统包含的则是更老的Ruby版本)。在OS X下有一款很流行的软件包管理工具,名为Homebrew,它能够帮助你替换成最新版的Ruby。你可能猜到了,需要首先安装Homebrew。在命令行下输入以下命令:

$ /usr/bin/ruby -e "$(curl -fsSL https: //raw. github. com/gist/ 323731)"

然后用这个简单的命令安装Ruby:

$ brew install ruby

Homebrew实际上正是一组Ruby脚本的集合。

一旦你按照前面介绍的方法装好了Ruby,就是该开始使用它的时候了!Ruby与C、C++、Java等需要编译的语言不同,在运行Ruby之前,你无须一个中间步骤来生成可执行文件。

运行Ruby代码有若干种方法,但入门的最简单方法大概就是使用安装过程中已内置的交互式Ruby工具。irb是一种Ruby REPL(读入-求值-打印循环)应用,它提供了交互式的编程环境,使你能够键入Ruby命令并实时地求出结果:

$ irb
ruby-1.9.3-p125 :001 > puts "hello world!"
hello world!
  => nil
ruby-1.9.3-p125 :002 >

请注意,一旦你输入了一条Ruby语句(在例子中,我们将字符串“hello world!”放到标准输出中),语句会立即被求值,使得“hello world!”被显示到屏幕上。在这之后,irb告诉你这条语句的值是空值(nil),因为Ruby中的puts语句返回一个空值。如果输入了这样的一条语句:

$ irb
ruby-1.9.3-p125 :001 > 1 + 1
  => 2
ruby-1.9.3-p125 :002 >

则会返回2,这正是它求值的结果。irb是一种能够快速上手的工具,而且,任何时候你如果不确定结果是什么,都可以向它寻求帮助。

另外一种运行Ruby的常见方式则是将代码保存为文件,之后通过Ruby解释器来运行文件。例如,你可以将“hello world!”保存到名为hello_world.rb的文件中。之后,你可以在命令行里输入这样的命令:

$ ruby hello_world.rb
hello world!

本书中的大部分示例都是通过这种方式来运行的。

在写一些简单的Ruby程序时,可能仅用Ruby自带的库就足够了;但更多的时候,需要一些外部库让开发更轻松。随Ruby预装的库有以下两个。

想要使用标准库或其他Ruby核心以外的库,需要在程序中用require语句来请求它们,例如:

require 'base64'

除标准库外,你将经常用到其他的外部库,或是由Ruby社区开发的,或者干脆是你自己开发的。最常用的发布Ruby库的手段是使用RubyGems——Ruby的包管理器。作为Ruby的一部分,它随标准库一起发布。因此,你可以在安装Ruby后立即使用它。

就像apt-get和yum管理Linux发行版中的软件包一样,RubyGems允许你轻松地安装或删除Ruby的库或应用程序。通过RubyGems发布的库或应用程序,需要被打包成称作gem的东西,其中包含一系列待安装的文件,以及对包进行描述的元数据文件。

gem可以在本地发布(通过扩展名为.gem的文件),也可以通过gem服务器远程发布。过去有一些公用的gem服务器为gem提供托管服务,包括RubyForge、GitHub和GemCutter。但最近它们不同程度地为RubyGems所取代。在RubyGems的术语中,gem服务器又被称做(source)。你可以部署自己私人的gem服务器,在其上发布预先打包好的、供内部使用的私有gem。

想在安装好的RubyGems中添加源,可以使用这条命令:

$ gem sources –add http://your.gemserver.org

若想安装本地gem,则可在控制台使用如下命令:

$ gem install some.gem –local

可以省略–local选项,但这样会多花一些时间,因为命令执行后会先搜索远程的源,使用“本地”(local)选项会告知RubyGems省略这一过程。

若想从远程的源中安装gem,通常可以这样做:

$ gem install some_gem

也可以通过下面的命令来安装某个gem的特定版本:

$ gem install some_gem –version 1.23

要列出本地已安装的所有gem,可以这样做:

$ gem list –local

安装结束后,我们就可以开始使用Ruby了!

1.字符串

处理字符串(string)是程序中通常要做的基本工作之一。任何名副其实的编程语言都会提供一些处理字符串的手段,Ruby也不例外。实际上,Ruby在这方面功能堪称强大。

Ruby中所谓的字符串,就是由字符组成的序列。有若干种方法来定义字符串。最常见的方法可能是用单引号(')或双引号(")把它们括起来。如果用双引号,可以在其中使用转义序列,或用#{代码}表达式来嵌入可执行的Ruby代码,实际包含在字符串中的是代码的执行结果,而并非代码本身。在被单引号括起来的字符串中,则不能这样做:

 "There are #{24 * 60 * 60} seconds in a day"
=> "There are 86400 seconds in a day"

'This is also a string'
=>'This is also a string'

字符串还可以通过%q和%Q来定义。%q与单引号作用相似,%Q则与双引号作用相似,唯一的区别在于,在%q和%Q之后可以采用任意成对的界定符来括起字符串,例如:

%q/This is a string/
=> "This is a string"

%q{This is another string}
=> "This is another string"

%Q!#{'Ho! ' * 3} Merry Christmas \!!
=> "Ho! Ho! Ho!  Merry Christmas!"

最后,你还可以使用here-document来定义字符串。here-document是一种在shell命令行(如sh、csh、ksh、bash等)下或一些脚本语言(如Perl、PHP、Python,当然,也包括Ruby)中界定字符串的方法。here-document会将输入文本中的换行符和其他空白字符(包括行首的缩进)原样保留:

string = <<END_OF_STRING
 The quick brown fox jumps
 over the lazy dog.
END_OF_STRING
=> "  The quick brown fox jumps\n  over the lazy dog.\n"

注意,here-document中的界定符是第一行中<<以后的字符串,在上例中,乃是END_OF_STRING

我虽然无法在本节中列出Ruby提供的所有字符串操作功能,但是可以给大家略举以下几例。

a = "hello "
b = "world"

a + b
=> "hello world"          # 字符串拼接(将a和b连在一起创建一个新字符串)

a << b
=> "hello world"          # 增加到字符串末尾(会修改a的值)

a * 3
=> "hello hello hello"    # 你可以使用“乘法”将一个字符串重复若干遍

c="This is a string"     # 以某个界定符为界限切分字符串,默认的界定符是空格
c.split
=> ["This", "is", "a", "string"]

2.数组和散列

与字符串同等重要,甚至可能更重要的,是操纵数据结构的能力。最重要的也是会在本书中(同样也在任何的Ruby编程工作中)经常出现的两种数据结构是数组和散列。

数组是带下标的数据容器,可存储一系列的对象。可以用方括号([])或Array类来建立数组。数组用以0开头的整数来索引,用[]操作符实现。

a = [1, 2, 'this', 'is', 3.45]
a[0] # 1
a[1]  # 2
a[2] # "this"

还有一些其他方法来索引数组,包括区间:

a[1..3]  # [2, 'this', 'is']

为数组的某个元素赋值时,我们同样使用方括号:

a[4] = 'an'
a #[1,2,'this','is','an']

数组元素可以是任意的对象,包括其他数组:

a[5] = ["another", 'array']
a # [1, 2, 'this', 'is', 'an', ['another', 'array']]

如果你对操纵数据结构比较熟悉,你可能会好奇我为什么在这一节里仅提及数组和散列。其他那些常见的数据结构(如栈、队列、集合等)哪儿去了呢?实际上,这些都可以用数组来实现:

stack = []
stack.push 1
stack.push 2
stack.push 'hello'
stack  # [1, 2, 'hello']

stack.pop # 'hello'
stack  # [1, 2]

还有许多其他方法可以被用在数组上,你可以在Ruby网站的参考文档中找到它们,或者更进一步,打开irb来玩一玩它们。一种常见的迭代遍历数组的手段是使用each方法:

a = ['This', 'is', 'an', 'array']

a.each do |item|
  puts item
end

上述程序会把数组的每个元素在标准输出(即控制台)中输出一遍。代码中的循环由do开始,以end结束,它对数组的4个元素各执行一次。这里,我们选择名叫item的变量来代表循环中的数组元素,并用竖线将变量名括起来。有时候,为了简洁,我们用一对大括号{}来替换do…end。上述代码会产生如下结果:

This
is
an
array

注意,数组元素是按照定义时的顺序输出的。

数组有众多的方法,你还应该了解的是,ArrayEnumerable(可枚举)模块的一个继承,该模块已经实现了那些方法。我们很快会讲到Enumerable模块。

散列可以被视为字典或映射,是一种可以索引一组对象的数据结构。与数组的主要不同在于,数组的下标是一个整数,而散列的下标可以是任意对象。散列可用大括号{}Hash类来定义,并用方括号来索引。

h = { 'a' => 'this', 'b' => 'is',  'c'=>'hash'}

h['a']     # "this"
h['b']     # "is"
h['c']     # "hash"

为散列的一个元素赋值同样是用方括号:

h['some'] = 'value'
h    # {'a' => 'this', 'b' => 'is', 'c' => 'hash', 'some' => 'value'}

给散列的键(即索引下标)赋值的散列火箭hash rocket[1]风格语法在Ruby 1.9版本中被做了改进。原有的语法仍然可用,但新的语法更简单、更清爽。下面的两行代码做的是同一件事情:

h = { canon: 'camera', nikon: 'camera', iphone: 'phone'}
# 等同于
h = {:canon => 'camera', :nikon=>'camera',:iphone => 'phone' }

有许多办法可以对散列表进行迭代,最常用的方法是:

h = { canon: 'camera', nikon: 'camera', iphone: 'phone'}

h.each do |key, value|
  puts "#{key} is a #{value}"
end

就像之前我们用竖线将表示数组元素的item变量名括起来一样,这里我们用竖线将两个变量名括了起来。第一个代表每一个散列元素的键,第二个则代表该元素的值。上述代码将产生如下结果:

canon is a camera
nikon is a camera
iphone is a phone

数组和散列都是从Enumerable(可枚举)模块继承而来,换句话说,它们都是Enumerable的子类。Enumerable是一个提供了具有一系列能力——包括一些遍历、查找和排序方法——的集合类型的模块。Enumerable提供的一个非常有用的方法是map方法,该方法针对集合的每一个元素,执行语句块中提供的操作,并且返回由每个操作结果组成的新数组。下例中map方法的输入是一个数字区间(从1到4),输出的是每一个输入数据的平方。

(1..4).map do |i|
  i*i
end   #[1, 4, 9, 16]

max_bymin_by方法也是很有用的。你可能能猜到,这两个方法可以返回一个数组的最大或最小值:

a = ["cat", "horse", "monkey"]
a.min_by {|i| i.length}    # "cat"
a.max_by {|i| i.length}    # "monkey"

3.符号

Ruby包含了符号(symbol)的概念,它们是一些名称常量。符号由冒号开头,跟着是一个名字。例如,:north:counter就是符号。符号在你需要某种类型的标识符的情形下十分有用。这时如果使用字符串,会造成浪费,因为每生成一个字符串,都要建立一个新的对象。而符号一旦定义,每次用到总会引用起初定义的唯一的那个对象。

4.条件和循环

如果你做过某种编程工作,那么对Ruby中的条件和循环就应该不陌生。Ruby具有直接或间接的C语言血统,因此它的条件语法跟C的语法十分类似。

if表达式和其他语言中的非常像:

if pet.is_a? Dog then
   wag: tail
elsif pet.is_a? Cat then
   meow
else
   do_nothing
end

在每条语句都独占一行的情况下,then关键字是可选的。if语句的否定和相反的形式是unless语句:

unless visitor.friend?
  bark :loudly
else
  wag :tail
end

有时,当没有else语句时,ifunless可以被用作条件修饰符,来表示语句会在条件被满足的情况下执行。

wag(:tail) if pet.is_a? Dog

bark(:loudly) unless visitor.friend?

前述代码中,wag(摇动)方法在pet(宠物)对象属于Dog(狗)类时才被执行。bark(叫)方法在访客(visitor)是一个朋友(friend)时才不被执行。

最后,像C语言一样,Ruby可识别三元条件表达式:

visitor.friend? ? wag(:tail) : bark(:loudly)

这等价于:

if visitor.friend? then
  wag(:tail)
else
  bark(:loudly)
end

在Ruby中,有两种使用case表达式的方法。一种是像一系列的ifelsif语句那样:

case
when visitor.friend?
  wag :tail
when visitor.postman?
  chase
when visitor.carries :big_juicy_bone
  jump_on visitor
else
  bark :loudly
end

第二种是更常见的,由case语句选定一个目标,每个when子句分别跟目标做一次比较:

case visitor.name
  when "Harry" then greet("Hello and welcome!")
  when "Sally" then greet("Welcome my dear!")
  when "Joseph" then greet("They are not here yet")
  else do_not_open_door
end

Ruby中两种主要的循环机制是while和它的否定形式untilwhile循环执行在条件满足时执行循环体,这意味着循环体可能被执行0次或以上。until则相反,反复执行循环体直到条件为真为止。

while visitor.hungry?
  offer food
end
# 等同于
until visitor.full?
  offer food
end

可以看出,两种形式做了完全相同的事情。那为什么要同时提供这两种形式呢?要记得Ruby是具有表现力的,并且常常努力让程序更为智能化。尽管两种形式是相同的,但有时用其中的某一种会比另一种更自然。

ifunless一样,whileuntil也可以被用作语句修饰符:

offer(food) while visitor.hungry?
# 等同于
offer(food) until visitor.full?

关于Ruby,你可能经常耳闻的一个说法就是:在Ruby中,一切都是对象。这听起来有点极端,并且在技术的观点上不完全正确。显然,关键字,如if-else条件语法,并不是对象。然而你操作的所有东西确实都是对象。就连类也是对象,方法也如此。而且,所有求值的结果也都是对象。下面我们来看看这种机制的运作情况。

1.类和对象

创建对象的经典方法是通过某个类进行实例化:

class Dog
   attr :breed, :color, :name

   def initialize(name, color, breed)
     @name, @color, @breed = name, color, breed
   end

   def bark(volume=:softly)
     make_a_ruckus(volume)
   end
end

如果你已经用其他语言做过某种形式的面向对象编程,这对你而言应该比较熟悉。如果你没有做过,那么就可能有一点迷惑,但很容易解释清楚。上面的代码定义了一个类,某种程度上好比一个模板,你基于它来生成实例或对象。在本例中,我定义了一个Dog(狗)类,它拥有breed(品种)和color(颜色)等属性,并且每一个类的实例拥有一个特定的名字。attr关键字是一个方法调用,帮助建立3个实例变量(breedcolorname)以及一些访问这些变量的标准方法。Ruby中的实例变量均以@开头。

def开头的几行定义了一些方法。方法是属于对象并且在对象上调用的函数。上例中有两个方法,即initialize(初始化)和bark(叫)。

initialize是一个便捷方法。每当Ruby建立一个新对象时,总是会寻找并调用名为initialize的方法。在我们的initialize方法中,通过参数传递的值为每一个实例变量赋值。

bark方法的功能很简单——制造一次“喧闹”。它的定义显示了如何为参数设定默认值(上例中为softly),默认值在调用时未给该变量传值的情况下起作用。

那么我们如何通过Dog类建立一个对象呢?

my_dog = Dog.new('Rover', :brown, 'Cocker Spaniel')

my_dog是一个变量,包含了由Dog类实例化的一个对象,括号里的几个值则经由initialize方法给出了对象的名字、颜色和品种。

2.方法

前已提及,可以用def关键字来定义方法,def后跟方法名。方法定义可以有任意数量的参数,也可以没有参数。如果你的方法不需要参数,你可以在定义时省略括号:

def growl
  make_a_ruckus{:very_softly}
end

通过前面的Dog类你可能已经注意到,你可以为方法的参数设定默认值时:

def bark(volume=:softly)
  make_a_ruckus(volume)
end

在上面的代码中,volume参数默认值是:softly这一符号量。当你为参数设定了默认值时,你可以在调用方法时包含或省略参数值:

my_dog.bark        # 此时bark方法自动使用默认值:softly
my_dog.bark(:loudly)

对于有多个参数的方法,通常的做法是将有默认值的参数放在没有默认值的参数之后。如果没有默认值的参数在后,设置默认值就变得没有意义了,因为每次调用时,所有参数都必须赋值。

方法总要返回一个值,该值可以是一个数组,这样就可以实现多个值的合并返回。返回值可以用return关键字来指定。如果到方法结束都没有遇到return,返回的将是方法中最后一个被求出的值。

3.类方法和变量

目前为止我们讨论了类的实例。前面的一个例子体现了从Dog类到my_dog对象的实例化。其中的变量和方法实际上都是属于my_dog对象的,并且也只能被my_dog对象所调用。举例来说,基于前面Dog类的定义,你不能这样做:

Dog.bark

从逻辑上讲,因为Dog类是创造各种狗对象的模板,调用Dog类的bark方法,意味着让所有的狗一起叫!然而,在某些情况下(如果你以前做过面向对象编程,你应该明白我在指什么),我们需要使用属于类而不是对象的方法甚至是变量。如何实现这个需求呢?

之前我提到,连类本身也是对象。我们下面要做的无非就是将一个类当做对象对待。要定义一个类方法,在方法名前面加上self即可:

class Dog
  attr :breed, :color, :name
  def self.total_count
    # 返回系统中狗的总数
  end

  # 其他方法
end

self是一个代表当前对象的关键字(与C++或Java类似)。当我们定义一个类的时候,当前对象就是这个正在定义的类。我们用self定义类中方法的时候,意味着将这个方法添加给类本身,而非类的某个实例。此时,我们是在为一个“类对象”,也即Class类的一个实例,添加方法。当我们需要定义作用在类本身上的方法时,将会经常用到这一手段。

定义类变量是一件很直接的事情,在变量名前面加上@@即可:

class Dog
   @@count = 0
   attr :breed, :color, :name

   def self.total_count
      @@count
   end

   def initialize
      @@count += 1
      # 其他初始化工作
   end

   #其他方法
end

注意,@@count这个类变量在类定义中被初始化为0,这项工作只被执行一次。通常,在initialize方法中初始化类变量是错误的,因为initialize方法在每个新的对象被建立时都要被执行一次。这意味着,每建立一个新对象,类变量的值都要被重置一次!

4.继承

继承是面向对象编程的基石之一。Ruby中的继承十分简便。要把一个类声明为另一个类的子类,只要在类定义中这样做:

class Spaniel &lt; Dog
  # 其他定义工作
end

这样就创建了名叫Spaniel的子类,它继承了Dog类的一切,包括方法和成员变量。有一个问题:如果Spaniel类是Dog类的一个子类,那么Dog类又是谁的子类呢?你可以通过在Dog类上调用superclass方法来找到答案。别忘了Dog类本身也是一个对象,所以可以在其上直接调用某个方法:

Spaniel.superclass        #Dog
Dog.superclass            #Object
Object.superclass         #BasicObject
BasicObject.superclass    #nil

可以看到,Dog类是Object类的一个子类(Object类本身也是一个对象,你是不是感觉有点混乱?),而Object类又是BasicObject类的一个子类。BasicObject则是继承关系的终点,乌龟下面并非总有别的乌龟![2]

我们已定义了Spaniel类,当调用它的bark方法时会发生什么呢?因为bark方法在Spaniel类中并未定义,系统将会上溯到它的超类,也就是Dog类,调用其中的同名方法。当然,如果Ruby在Dog类中也找不到bark方法,则会沿继承层次继续上溯,直到BasicObject类,如果仍未找到,则抛出一个NoMethodError(找不到方法)异常。

你不能用一个子类同时继承多个超类。有些编程语言支持多重继承,但Ruby仅支持单继承。然而,Ruby提供了模拟多重继承的机制——mixin机制,使用模块来实现。

模块是一种将一些方法、类和常量组合在一个名字空间里,避免命名冲突的手段。如果在Ruby的类中包含了模块,那么就意味着让mixin机制成为可能。因为当类中包含多个模块时,我们就可以模仿多重继承的效果。

让我们继续Dog类的例子,为Dog类定义一个名叫Canine(犬科动物)的超类:

class Canine
  # 一些定义
end

class Wolf &lt; Canine
  # 一些定义
end

class Dog &lt; Canine
  # 一些定义
end

我们知道,狗也是宠物,那么如果我们想将一些方法和变量组合成一个Pet(宠物)类,我们怎样能让Dog类继承这些方法和变量呢?在Ruby中,我们不能直接这样做,因为它只支持单继承,但我们可以将Pet变成一个模块:

module Pet
  def scratch_stomach
    # 这是一只好宠物!
  end
end

class Dog &lt; Canine
  include Pet
  # 其他一些定义
end

用这种手段,Dog类就可以在不违背单继承机制的情况下,同时继承PetCanine的方法了。

关于mixin机制还有另外一个例子,记不记得在1.1.5节的“数组和散列”部分,我们说过Array类和Hash类都包含了Enumerable模块。

5.“像鸭子一样编程”

Ruby和Python、PHP、Smalltalk等语言,都是为人熟知的动态类型语言,而C和Java等则是静态类型语言。本质上,如果一个语言需要程序员在代码中指定数据类型,并且编译时会进行类型检查,在类型不匹配时报错,那么它就是静态类型语言。与此相反,动态类型语言不需要在代码中指定数据类型,并将类型检查留到运行时进行。

例如,在Java中,你需要先声明变量再给它赋值。

int count = 100;

然而,在Ruby中,你只需要:

count = 100

此时,你应当自觉地正确使用这个变量,这意味着,当你为变量赋了整数值时,你应当在代码中把它作为整数来用。当你使用count变量时,Ruby明白它是一个整数,并且你也应当把它当整数使用。如果你不这样用,Ruby会自动将它转换成你试图去把它视为的任何类型。这个机制被称作鸭子类型

鸭子类型背后的思想来自所谓的鸭子测试:如果一个东西走路像鸭子,叫声也像鸭子,那么它就是一只鸭子。这意味着,对象的类型并不由对象所属的类决定,而是由该对象可以做什么来决定。

举一个简单的例子,我们定义名叫op的方法:

def op(a,b)
  a &lt;&lt; b
end

这个方法接受两个参数,并返回单个值。方法中既未指定参数类型,也未指定返回值类型。这样做是否会带来潜在的bug呢?我们来看看如何使用这个方法。如果xy都是字符串,返回值也将是一个字符串,此时没有问题:

x = 'hello'
y= 'world'

op(x,y)
=> 'hello world'

x是个数组、y是个字符串时,方法将y追加到x末尾,从而返回一个新的数组:

x = ['hello']
y= 'world'

op(x,y)
=> [ "hello", "world"]

xy都是整数时,方法会完成一个左移位操作,将二进制的1向左移动两位,从而得到4(二进制的100):

x = 1
y = 2

op(x,y)
=> 4

这意味着什么呢?鸭子类型机制是瑕瑜互见的。最明显的缺点是使方法缺乏一致性:在传入不同的值时,方法的结果会大相径庭,这一点在程序实际运行之前都不会被检查。

一个主要的好处则是带来了更简单的代码。如果你明确知道你在做什么,它将带来更易读和易维护的代码。

最后我们要说,鸭子类型不仅仅是Ruby中一种固定的编程机制,它更像是一种哲学。如果你想确保op方法只能在参数是字符串时才可用,你可以这样做:

def op(a, b)
  throw "Input parameters to op must be string"
    unless a.is_a? String and b.is_a? String
  a &lt;&lt; b
end

如果ab并非字符串,程序将抛出一个异常。

本章的第二部分将介绍Shoes——一个基于Ruby的用户界面工具包。Shoes通常并不被用作用户界面工具,而且Ruby本身也并不以建立桌面图形界面见长。其他更为流行的Ruby用户界面工具包有FXRuby、WxRuby、qtRuby和Tk。如果要求完全跨平台,JRuby加Swing的组合是个好选择。尚有不少可以代替Swing的选择,比如SWT和Limelight。在Mac平台上,MacRuby则是个很好的备选。

但在本书中,我们将使用Shoes。

Shoes是个基于Ruby的、用于编写图形程序的、跨平台的工具包。Shoes是完全地、单纯地基于Ruby的,这一点与多数其他工具包不同。其他工具包一般是Ruby和其他UI工具包的混合体。而且Shoes上手非常容易,这是本书使用它的主要动因。

Shoes最初的创作者叫做why the lucky stiff(是的,这就是他的名字),一个相当著名和神秘的Ruby程序员。他还兼做卡通绘制和音乐演奏。他出名的最主要因素可能是写作了Why’s (poignant) Guide to Ruby,这是一本完全不像编程书籍的教授Ruby编程的书。

由于未知的原因,_why(这是他的别称)在2009年8月注销了他的Twitter和GitHub[3]账号,并关闭了他的个人网站,这都曾经是Ruby程序员们热衷出没的场所。但是,他完成的许多项目,包括Shoes,被Ruby社区收纳并传承下去。

红宝石(Ruby的英文原义)是红色的,所以鞋(Shoes的英文原义)也照样。Red Shoes(英文意为“红色的鞋子”)是Shoes的最初版本,它由_why基于C语言编写。_why离开之后,Ruby社区接管了它,在其基础之上,涌现出了不同类型的Shoes,每种都以不同的颜色来命名。

本书中提到的Shoes都是指Red Shoes。事实上,Red Shoes也是我唯一实际跑过代码的Shoes版本。所以,我要做一个“标准免责声明”,即后面那些代码不能保证在其他版本的Shoes上也能正确运行。但是,非常欢迎你作些这方面的尝试。

Shoes的安装通常十分简单。如果你使用Mac或Windows系统,只要从Shoes的网站(http://shoesrb.com/downloads)上下载对应平台版本并安装即可。然而Shoes的使用有些反传统,并不像大多数Ruby程序那样通过控制台运行。你需要打开Shoes应用程序,用它来打开和运行你的Shoes程序。可以在Mac或Windows系统中等效地完成下面的工作:

$ /Applications/Shoes.app/Contents/MacOS/shoes test_shoes.rb

如果你使用某种Linux,Shoes的安装会更复杂一些。截至本书写作时,最好的方法是从源代码来完整地生成它。这并不算很复杂,只是需要先安装一些Shoes要依赖的其他的库。这里列出各种Linux下通用的安装步骤:

$ git clone git://github.com/shoes/shoes.git
$ cd shoes
$ gem install bundler
$ bundle install
$ rake

更详细的信息可以参考这个网站:https://github.com/shoes/shoes/wiki/Building-Shoes-on-Linux。

我在本书中选择Shoes作为用户界面开发工具的一个主要原因是,它可以相当简便地开发很好的图形界面。因为它是纯Ruby的(与那些Ruby和其他工具的混合体相反),代码的可读性强,易于理解。

一个简单的Shoes程序会像下面这样:

Shoes.app do
  button("Click me!") do
    alert("Ruby rocks!")
  end
end

此程序生成一个简单的窗口,其中只有一个名为“Click me!”的按钮。点击该按钮,会弹出一个警告对话框,如图1-1所示。

图1-1 一个简单的Shoes程序

尽管Shoes是一个简单的UI工具,但还是有很多特性,不可能用几节文字就介绍完整。我只能提供一些基本的例子。让我们从建立一个简单的秒表程序开始。

1.Shoes秒表

在例1-1中,我会展示如何用Shoes建立一个非常简单的秒表。

例1-1 Shoes秒表

Shoes.app height: 200, width: 200 do
  background lightblue
  stack margin: 10 do
    caption strong "Shoes Stopwatch"
    flow do
      button "start" do
       @time = Time.now
       @label.replace "Started at #{@time.strftime '%l:%M:%S %p'}"
      end
      button "stop" do
         @label.replace "Stopped, ", strong("#{Time.now - @time}"),
          " seconds elapsed."
      end
   end
   @label = para "Press ", strong("start"), " to begin timing."
 end
end

所有的Shoes应用代码都必须包含在一个对Shoes.app的调用中。你可以随意设置起始窗口的外观。本例中,我们设定了窗口的宽和高。第二行[9]代码则设定了窗口的背景颜色。这并不总是必需的,但注意lightblue(浅蓝色)是Shoes窗口的预设颜色。Shoes 借鉴了X11[10]和HTML[11]调色板定义的一系列默认颜色方案,并用直观的名字来代表它们。如果你倾向于建立自己的自定义颜色方案,可以利用rgb方法。

Shoes应用程序中的元素用(slot)来安排,它作为容器,可以盛载若干元素,或者其他槽。槽还可以被嵌套,通过对槽和元素进行嵌套可以编制相当复杂的布局。槽有两种常见类型,即(stack)和(flow)

你也可以为槽设置配置参数。秒表例子中的栈就通过参数设置了10个像素宽的边距。

button(按钮)元素为应用程序生成一个按钮。如果在元素中加入像例子中所示的代码块,当单击按钮时,就会执行其中的代码。另一种设置按钮点击后行为的方法是使用click方法,稍后我们会讲到。

执行先前的代码,你可以看到图1-2所示的秒表。

图1-2 Shoes秒表

这是个相当传统的用户界面,让我们来做点更有艺术含量的事情。

2.Shoes涂鸦程序

我们来编写一个简单的涂鸦程序。例1-2中的这个程序是个标准的演示程序,并没有多少使用价值,但阐明了Shoes的一些基本概念。

例1-2 Shoes涂鸦程序

Shoes.app do
  fill red
  orig_left, orig_top = nil,nil
  animate 24 do
    button, left, top = self.mouse
    line(orig_left, orig_top, left, top) if button == 1
    star(orig_left, orig_top, 5, 15, 5) if button ==3
    orig_left, orig_top = left, top
  end
end

这个程序看上去比秒表程序还要简单。让我们从描述animate方法入手来理解它。这个方法启动一个动画计时器,与程序剩下的部分并行执行。我们指定了当动画循环开始后每秒显示的帧数,所以程序会无限地循环下去。你可能会想到,这对于模拟运行而言是一个完美的方法。

self.mouse方法返回一个由3个数组成的数组。第一个数是按下的鼠标按键的编号。如果没有键被按下,这个值将是0。第二个和第三个数表示鼠标光标的左端和上端的位置。我们将这3个数分别赋给变量buttonlefttop

如果鼠标左键(即1号键)被点击,程序会画一条从光标原来位置出发,到光标当前位置的线段。因为我们处在动画循环中,如果我们连续移动鼠标,就会产生在屏幕上绘画的效果。

相似地,如果点击3号按键(通常是滚轮键),程序会画一个五角星。因为我们定制了所有画出的图形的填充颜色为红色,我们将会在屏幕上画出红五星。程序运行结果的一个示例如图1-3所示。

图1-3 Shoes涂鸦程序

把关于Ruby的全部内容放在一本书里是不可能的,更不用说一章文字了。我在这里所想做的是介绍这门语言,并提供对Ruby可以做的事情的简单一瞥。我跳过了许多激动人心的特性,包括人们津津乐道的元编程(metaprogramming)能力。关于这个主题足够写一本书。我还提供了简单但功能强大的用户界面工具——Shoes的简介,并给出了几个用它编写图形界面程序的例子。我在本章介绍的内容将是个好的开始,它们已经为你理解书中剩余部分的代码打下了足够的基础。

前进吧!

[1]  赋值符号=>形似火箭——译者注

[2]  出自史蒂芬•霍金的《时间简史》(Bantam出版社):
一个著名的科学家(有人说是伯特兰•罗素),曾经作过一次关于天文学的公开演讲。他描述了地球如何围绕太阳公转,而太阳又是如何围绕由众多星体组成的银河系的中心公转。在演讲结束的时候,一位坐在房间后方的老太太站起来说:“你讲的都是胡说八道,我们的世界是一个驮在巨龟背上的平板”。科学家莞尔一笑,回答说:“那么那只乌龟又站在什么上面呢?”“你很聪明,年轻人,很聪明。”老太太说,“但乌龟下面还有乌龟!”

[3] GitHub是一个为Git库提供托管服务的网站,Git则是流行的版本控制系统之一。——译者注

[4]  一个基于Ruby的行为驱动测试包。——译者注

[5] Qt是一个功能强大的跨平台的C++图形界面开发框架。——译者注

[6] GTK是一套跨平台图形界面工具包。——译者注

[7] Cario是一套矢量图形绘图库。——译者注

[8] Swing是一套用于开发Java应用程序用户界面的开发工具包。——译者注

[9] 原文中代码的按行计数方式与中文文献中的习惯有所不同,译者对之稍做了修改。—译者注

[10] 又名X窗口系统(X Window),是一套图形界面操作系统的标准工具包和协议。—译者注

[11] 超文本标记语言,当前互联网网页的标准描述语言。——译者注


相关图书

Ruby程序员修炼之道(第2版)
Ruby程序员修炼之道(第2版)
“笨办法”学Ruby(第3版)
“笨办法”学Ruby(第3版)
面向对象设计实践指南:Ruby语言描述
面向对象设计实践指南:Ruby语言描述

相关文章

相关课程