程序人生

写优雅的程序,做优雅的人

Ruby 2.0的新特性,用例子说明

| Comments

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 变得愈发性感了,我喜欢。

参考资料

Comments