Ruby 2.0 发布已经有一段时间了,之前从各种报道上大概了解到它的一些主要特性,但是没有认真仔细研究,所以印象并不深。这个周末好好研究了一番,写下这篇 Blog,算是这次学习的笔记。
Ruby 2.0 升级变动并不是很大,至少比 Ruby 1.8 到 1.9 的变动小,之所以把版本号定为 2.0,是为了纪念 Ruby 诞生 20 周年,所以特意选择了 Ruby 诞生 20 周日的日子 – 2013年2月24日发布。
虽然说变化不是特别大,但是新的特性还是挺让人兴奋的,因为它们对开发带来不少便利,让 Ruby 变得越来越性感。主要的新特性有 4 个,下面一一讲解。
1. Keyword Arguments
Keyword Arguments
特性让 Ruby 2.0 开始支持关键字参数,这对处理有默认值的参数带来非常大的便利。相比于以前使用 Hash 传值方法,Keyword Arguments 可以让代码更直观简洁。
例子1:
1
2
3
4
5
6
7
8
9
10
11
12
# Ruby 1.9:
# (From action_view/helpers/text_helper.rb)
def cycle(first_value, values)
options = values.extract_options!
name = options.fetch(:name, 'default')
# ...
end
# Ruby 2.0:
def cycle(first_value, values, name: 'default')
# ...
end
例子2:
1
2
3
4
5
6
7
8
9
10
11
12
# Ruby 1.9
def render(source, opts = {})
opts = {fmt: 'html'}.merge(opts)
r = Renderer.for(opts[:fmt])
r.render(source)
end
# Ruby 2.0
def render(source, fmt: 'html')
r = Renderer.for(fmt)
r.render(source)
end
例子3:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Ruby 1.9
def accepts_nested_attributes_for(attr_names)
options = {
:allow_destroy => false,
:update_only => false
}
options.update(attr_names.extract_options!)
options.assert_valid_keys(
:allow_destroy,
:reject_if,
:limit,
:update_only
)
# ...
end
# Ruby 2.0
def accepts_nested_attributes_for(attr_names,
allow_destroy: false,
update_only: false
reject_if: nil,
limit: nil
)
# ...
end
2. Refinement
Refinement 的目标是通过减少补丁的应用范围使打动态补丁(monkey patching)更为安全。下面是由Matz给出的一个例子,MathN模块包含进来之后“/”操作符才能在Fixnum上使用:
1
2
3
4
5
6
7
8
9
10
11
12
13
module MathN
refine Fixnum do
def /(other) quo(other) end
end
end
class Foo
using MathN
def foo
p 1 / 2
end
end
Rails 中有不少对 Ruby 的 monkey patching,使用 Refinement 特性重写的话可以让这些代码更安全。 但是目前 Refinement 还不是很成熟,属于体验特性,所以最好不要在生产环境使用。
3. Module Prepend
Module Prepend
特性让常见的 alias_method patten 扩展一个已用方法的写法变得简洁不少。
例如,下面的代码想对 Template 的 render 方法扩展计时钩子,使用 Ruby 1.9 的写法非常臃肿。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Template
def initialize(erb)
@erb = erb
end
def render values
ERB.new(@erb).result(binding)
end
end
module RenderProfiler
def self.included base
base.send :alias_method, :render_without_profiling, :render
base.send :alias_method, :render, :render_with_profiling
end
def render_with_profiling values
start = Time.now
render_without_profiling(values).tap {
$stderr.puts "Rendered in #{Time.now - start}s."
}
end
end
class Template
include RenderProfiler
end
Template.ancestors
#=> [Template, RenderProfiler, Object, Kernel, BasicObject]
使用 Ruby 2.0 的写法将变得非常简洁。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module RenderProfiler
def render values
start = Time.now
super(values).tap {
$stderr.puts "Rendered in #{Time.now - start}s."
}
end
end
class Template
prepend RenderProfiler
end
Template.ancestors
#=> [RenderProfiler, Template, Object, Kernel, BasicObject]
注意 include 和 prepend 的区别在于,执行后 ancestors 有明显不同,include 置于后方,而 prepend 置于前方,这就导致了方法查找路径的差异,从而导致 super
执行结果的差异。
4. Lazy Enumerable
Lazy Enumerable 可以让 Enumerable 不立即执行,这对函数式编程大有用处,例子如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def natural_numbers
(1..Float::INFINITY).lazy
end
def primes
natural_numbers.select {|n|
(2..(n**0.5)).all? {|f|
n % f > 0
}
}
end
primes.take(10).to_a
#=> [1, 2, 3, 5, 7, 11, 13, 17, 19, 23]
除了上面 4 大特性,还有一些小的改变,比如:
- 默认使用
utf-8 encoding
解析代码 - Symbol 数组
%i(a b c)
–>[:a, :b, :c]
- 新的 GC
- Ruby的性能也有所提升
- …
Ruby 2.0 让 Ruby 变得愈发性感了,我喜欢。
参考资料