Ruby 使用了 Symbols 和 Hash 来达到虚拟关键字参数(Pseudo-Keyword Arguments)。这种技巧被广泛应用在 Ruby 的函式库和 Rails 中,增加了阅读性,也很容易使用。
def blah(options)
puts options[:foo]
puts options[:bar]
end
blah(:foo =>"test", :bar =>"test")
Ruby 也可以将参数列当成阵列使用:
def sum(*args)
puts args[0]
puts args[1]
puts args[2]
puts args[3]
end
sum(1,2,3)
如此就可以设计出不固定参数列、十分弹性的 API。类似於 C++ 的 function overloading。在 Rails 中也十分常见这样的 API 设计,例如 link_to 就支援了两种用法:
# USAGE-1 without block
<% link_to 'Posts list', posts_path, :class =>'posts' %>
# USAGE-2 with block
<% link_to posts_path, :class =>'posts' do %>
Posts list
<% end %>
搭配虚拟关键字参数使用的话,可以参考 ActiveSupport#extract_options! 这个小技巧取出 Hash 值。
2. Code Blocks
程式区块(Block)是 Ruby 最重要的特色,除了拿来做迭代(Iteration)之外,也可以包装前後置处理(pre- and Post-processing),一个最基本的例子就是开档了,一般程序式的写法如下:
f = File.open("myfile.txt", 'w')
f.write("Lorem ipsum dolor sit amet")
f.write("Lorem ipsum dolor sit amet")
f.close
使用 Block 之後,我们可以将 f.close 包装起来,不需要明确呼叫。只要程式区块结束,Ruby 就会自动关档。程式一来因为缩排变得有结构,二来也确定档案一定会关闭(不然就语法错误了)
# using block
File.open("myfile.txt", 'w') do |f|
f.write("Lorem ipsum dolor sit amet")
f.write("Lorem ipsum dolor sit amet")
end
另一个程式区块的技法,是用来当做回呼(Dynamic Callbacks)。在 Ruby 中,程式区块也是物件,於是我们可以将程式区块如透过”注册”的方式先储存下来,之後再依照需求找出来执行。例如在 Sinatra 程式中:
get '/posts' do
#.. show something ..
end
post '/posts' do
#.. create something ..
end
我们”注册”了两个回呼:一是当浏览器送出 GET ‘/posts’ 时,会执行 show something 的程式区块,二是 POST ‘/posts’ 时。
3. Module
模组(Module)是 Ruby 用来解决多重继承问题的设计。其中有一招 Dual interface 值得一提:
module Logger
extend self
def log(message)
$stdout.puts "#{message} at #{Time.now}"
end
end
Logger.log("test") # as Logger’s class method
class MyClass
include Logger
end
MyClass.new.log("test") # as MyClass’s instance method
Ruby 的 extend 作用是将模组混入(mix-in)进单件类别(singleton class),於是 log 这个方法除了可以像一般的模组被混入 MyClass 中使用,也可以直接用 Logger.log 呼叫。
要将 Ruby 模组的混入成类别方法(class method),也有一些常见的 pattern 模式,可以将模组设计可以同时混入实例方法(instance method)和类别方法,请参阅投影片范例。这在撰写 Rails plugin 时非常常用。
4. method_missing?
Ruby 的 Missing 方法是当你呼叫一个不存在的方法时,Ruby 仍然有办法处理。它会改呼叫 method_missing 这个方法,并把这个不存在的方法名称传进去当做参数。这个技巧在 Rails 的 ActiveRecord 中拿来使用:
class Person <ActiveRecord::Base
end
p1 = Person.find_by_name("ihower")
p2 = Person.find_by_name_and_email("ihower", "[email protected]")
其中 find_by_name 和 find_by_email 就是这样的方法。不过这个技巧不是万能丹,它的执行效率并不好,所以只适合用在你没办法预先知道方法名称的情况下。不过也不是没有补救之道,如果同样的方法还会继续呼叫到,你可以在 method_missing 之中用 define_method 或 class_eval 动态定义此方法,那麼下次呼叫就不会进来 method_missing,进而获得效能的改善。事实上,ActiveRecord::Base 的 method_missing 就是这麼做的。(感谢 BigCat 留言提醒我有此补救之道)
另一个 Missing 方法的绝妙 API 设计,是拿来构建 XML 文件:
builder = Builder::XmlMarkup.new(:target=>STDOUT, :indent=>2)
builder.person do |b|
b.name("Jim")
b.phone("555-1234")
b.address("Taipei, Taiwan")
end
# <person>
# <name>Jim</name>
# <phone>555-1234</phone>
# <address>Taipei, Taiwan</address>
# </person>
搭配了区块功能,就能用 Ruby 语法来写 XML,非常厉害。
5. const_missing
除了 method_missing,Ruby 也有 const_missing。顾名思义就是找不到此常数时,会呼叫一个叫做 const_missing 的方法。现实中的例子有 Rails 的 ActiveSupport::Dependencies,它帮助我们不需要先载入所有类别档案,而是当 Rails 碰到一个还不认识的常数时,它会自动根据惯例,找到该档案载入。
我们也可以利用这个技巧,针对特定的常数规则来处理。例如以下的程式会自动将 U 开头的常数,自动转译成 Unicode 码:
class Module
original_c_m = instance_method(:const_missing)
define_method(:const_missing) do |name|
if name.to_s =~ /^U([0-9a-fA-F]{4})$/
[$1.to_i(16)].pack("U*")
else
original_c_m.bind(self).call(name)
end
end
end
puts U0123 # ģ
puts U9999 # 香
6. Methods chaining
方法串接是一个很常见的 API 设计,透过将方法的回传值设成 self,我们就可以串接起来。例如:
[1,1,2,3,3,4,5].uniq!.reject!{ |i| i%2 == 0 }.reverse
# 5,3,1
7. Core extension
Ruby 的类别是开放的,可以随时打开它新增一点程式或是修改。即使是核心类别如 Fixnum 或是 Object(这是所有类别的父类别) 都一样。例如 Rails 就定义了一些时间方法在 Fixnum 里:
class Fixnum
def hours
self * 3600 # 一小时有多少秒
end
alias hour hours
end
Time.now + 14.hours
Ruby 的物件模型与元编程(Meta-programming)
在 Ruby 中,所有东西都是物件。甚至包括类别(class)本身也是物件。这个类别物件(class object)是一个叫做 Class 的类别所实例出来的物件。而所有的物件(当然也包括类别物件),都有一个 metaclass (又叫做 singleton, eigenclass, ghost class, virtual class 等名字)。定义在 metaclass 里的方法,只有该物件能够使用,也就是 singleton method (单件方法),只有该物件才有的方法。
了解什麼是 metaclass 是 Ruby 元编程的一个重要前提知识。Ruby 元编程最常用的用途,就是因应需求可以动态地定义方法,例如在 Rails ActiveRecord 中常见的 Class Macro 应用。
要能随心所欲动态定义方法的关键重点,就是 variable scope (变数的作用域) 了。例如以下我们透过 class_eval 和 define_method 帮 String 定义了一个 say 方法,注意到整个 variable scope 都是通透的,没有建立新的 scope:
name = "say"
var = "it’s awesome"
String.class_eval do
define_method(name) do
puts var
end
end
"ihower".say # it’s awesome
class_eval 可以让我们改变 method definition 区域(又叫做 current class)。除了本投影片,建议可以阅读 Metaprogramming in Ruby: It’s Allhe Self 和 Three implicit contexts in Ruby 这两篇文章深入了解 self 和 current class。
8. Class Macro (Ruby’s declarative style)
Class Macro 是 Ruby Meta-programming 非常重要的一个应用,例如在 Rails ActiveRecord 中:
class User <ActiveRecord::Base
validates_presence_of :login
validates_length_of :login,:within =>3..40
validates_presence_of :email
belongs_to :group
has_many :posts
end
伴随着RoR的风行,Ruby语言受到越来越多的开发者的关注,同为脚本语言,Python的地位却略显尴尬,什么样的原因,造成了这样的局面?笔者认为有以下几个方面:
一、 RoR的推波助澜
笔者认为,Ruby的成功,很大一部分是由于RoR的带动。
几年前,如果你没听过RoR倒是情有可原,但如果今天,RoR对你来说,还是一个陌生词汇的话,那你就有点危险了。:)
什么是RoR呢?
全称,Ruby on Rails,简称,RoR或者Rails。
它是个全栈的(full-stack)web应用框架,它为开发者提供了构建一个web应用所需的完整基础结构,并且严格按照MVC(模型-视图-控制器)架构进行开发。
RoR致力于提高开发者的开发效率,希望通过尽量少的代码,完成尽可能多的功能。基于这样的考虑,RoR有两大设计原则,一是,不要重复自己 (Don''t Repeat Yourself);二是,惯例优于配置(Convention Over Configuration)。
使用RoR,你甚至可以通过简单的几条命令行、几行代码,就完成一个功能强大的web应用程序,这极大地提高了开发者的开发效率。
2004年7月,RoR一经发布,在短期内,便受到很多开发者的追捧。时至今日,RoR已经被全世界的开发者们所关注,它带给开发者的效率提升,是前所未有的;它带给业界关于软件开发的思考,也是意义深远的。
在RoR如此风行的大背景下,Ruby on Rails,这个需要使用Ruby进行开发的web框架,也自然地带动了Ruby语言的发展。
在下面的“Ruby语言受关注程度趋势图”上,可以清晰地看出,Ruby语言在RoR发布也就是2004年7月后,进入了高速发展期。
Ruby因为有了RoR这样的“杀手级”应用,变得春风得意,那么Python呢?
在《浅谈Python语言》一文中,我们提到了Python具有丰富的API库,在web开发方面,也有Django、Turbogears这样的一些框架,就运行速度而言,
Python比Ruby快;就社区而言,Python也比Ruby成熟,可是为什么Python没有产生一个像RoR这样的“杀手级”应用呢?
Python语言的创始人Guido在接受InfoQ采访时,是这样解释的:
“我不喜欢Killer
Application,因为那会让多数人或者社区将精力集中于一个地方。Python是一个应用广泛的语言,基于Python已经产生了很多好用的
Web框架,比如Django等。但是Python不Killer
Application,至少目前是这样,而且我相信随着Python社区的发展,会有很多Killer
Applications自然出现。我喜欢多样化的应用。”
通过这个观点,我们可以看出Python在其发展道路上,追求的是一种均衡,一种“大而全”。
“会有很多Killer Applications自然出现”,这点,我想我们需要拭目以待。单就“均衡”而言,笔者认为这很危险,作为一门脚本语言,试图做Java之类传统语言做的事情,不太可取。
以Java为例,在web应用上,有太多的框架可供选择,不错,“在不同的场景用不同的框架”,这想法很好。可是,在实际应用中,有多少开发者可以根据项目特点,正确、合理地选择框架?
与其到最后,用户还不知道该如何选择,还不如一开始就替用户做出一个选择。
作为Python的使用者,我更愿看到有个Python的“杀手级”应用出现,进而带动Python更快速地发展。
你可以说RoR成就了Ruby,可是Rails为什么偏偏选择了Ruby?!Ruby优势何在?
二、 Ruby的优势
1. 比Perl更强大,比Python更面向对象
“比Perl更强大,比Python更面向对象”,这是Ruby创始人Matz设计Ruby的初衷。
Python既支持面向过程的编程也支持面向对象的编程,而Ruby则是完全面向对象。
在Ruby中,任何东西都是对象,包括Python中的基本数据类型;每个过程或函数都是方法。
例如,取-3的绝对值,在Python中,是这样的:abs(-3)。
而在Ruby中,则是这样的:-3.abs。这种OO的方式,显得更加直观。
2. 强大的语法功能
单就语法的简单性而言,Ruby不及Python。但解决一些较复杂的问题,Ruby强大的语法功能,有助于降低问题的复杂度。
例如:
Ruby以“块”的方式来实现列表内的条件、循环语句,比Python的更灵活、更具通用性。
Ruby具有类似Lisp的彻底的函数方式的条件、循环语句等。
Ruby的迭代器功能可以将流程控制结构抽象化。
3. 强大的字符串处理、正则表达式功能
Matz认为:Ruby >(Smalltalk + Perl) / 2。
Ruby类库是对Perl语言功能的面向对象方式的重组,因为借鉴了很多Perl的东西,使得字符串处理、正则表达式这块,Ruby同样强大。
4. 不会僵住的“胶水语言”
同样是“胶水语言”,Ruby比Python更灵活。
使用过一段时间的Python,你会发现,Python比较依赖第三方的东西。相比较,Ruby则更依赖自身。例如,Ruby可以使用(UNIX的)绝大部分的系统调用,单独使用Ruby也可以进行系统编程等。
有优势,Ruby就一定可以成功了?Python同样也有很多优势!对,还得看当时所处的环境。
三、 时势造英雄
这点还得回到RoR的崛起。
Ruby,1995年12月正式发布,2000年进入美国;2004年7月,RoR正式发布。
RoR的出现时间,值得玩味。
在下面的“Java语言受关注程度趋势图”上,可以看到,2004年7月前后,Java处于一个相对“衰退期”。
提到Java,大家应该都会想到J2EE。
J2EE应用程序的广泛实现是从1999、2000年开始的,它的出现带来了诸如事务管理之类的核心中间层概念的标准化,但是因其开发效率、学习难度和实 际性能的问题,在实践中没有获得完全的成功。作为J2EE核心技术的EJB(2.x),更是因其高昂的学习代价、极低的开发效率和极高的资源消耗,备受指 责。
在这样一个大背景下,2003年,Spring框架诞生了。
Spring的设计思想在于“使J2EE开发更加简单”。这个设计思想,在包括Java领域在内的众多软件开发领域引起了广泛关注。软件开发者们开始思考,如何让开发向着一个更简单的方向发展。
RoR在这一时期出现,无疑是顺应了这样一个潮流。
可以这么说,是历史选择了RoR,当然也选择了Ruby。
遇到合适的机遇,还不够,俗话说得好:“众人拾柴火焰高”。
四、 众星捧月
Ruby的出现,受到了两大主流平台Java和.NET的极力追捧。
2006年9月,SUN雇佣了JRuby的主要开发者Charles Nutter和Thomas Enebo;一年不到,2007年6月14号,JRuby 1.0正式发布。SUN在其Java IDE NetBeans 6.0 M10中,更是集成了对Ruby/JRuby的支持。反观Python的Java实现——Jython,则没有这么幸运,发展至今,它并没有得到SUN的 支持。
而Microsoft,也在2007年7月,推出了Ruby的.NET实现——IronRuby的预览版。
相信SUN和Microsoft对Ruby的竞相推崇,必然推动Ruby的进一步发展。
基于以上几点,笔者认为Ruby的成功不是偶然,并且相信这样的成功还会持续下去。对于Python的未来,我们也将拭目以待。