arr = [1, 2, 3, 4, 5]
arr.each do |i|
  puts i
end

arr.each {|i| puts i+1}

def test(arr)
  yield(arr)
end
test(arr){|i| puts i}

看这个例子中,do...end 和 {} 包围的部分,就是一个block。中文翻译为「块」。

Ruby中的block,不是Ruby首创。Ruby深受Lisp和SmallTalk等语言的影响,block在这些语言中也存在,只是Ruby把它实现的更为极致。

比如,在其他语言中,你必须显式的指定一个函数是可以接受一个函数作为参数的,但是在Ruby中,任意一个方法,都可以将block作为一个隐性的参数去调用。

def test
  puts 'hello'
end

test{ puts "world" }
#=> "hello"

上例中,test方法并没有定义参数,但是它可以接收一个block,block天生就是方法的一个隐式参数。

但是那个例子并未在方法中执行那个block的代码。

如果想要执行,必须通过yield关键字。

def test
  puts "hello"
  yield
end

test{ puts  "world!"}
#=> "hello" "world!"

注意: block,不是一个对象,因为它不能脱离于方法独立存在。将它对象化以后,就可以独立的调用了。

闭包

block如何对象化?

Ruby可以通过lambda方法来创建一个block对象,也叫做闭包对象。

lamd = lambda{|i| puts i}
lamd.call(3)
#=> 3

为什么叫闭包对象呢?

我们上一节讲过scope(作用域)的概念,class、def、module关键字会建立一个相互隔离的域。

class Person
  foo = "world"
  def say
    puts "hello #{foo}"
  end
end

person = Person.new
person.say #=> NameError: undefined local variable or method `foo' for #<Person:0x007febd47c8a68>

像上面的例子,当调用实例方法的时候,报错, 意味着,在def域里面无法调用class域的本地变量(也叫局部变量)。

如果想调用怎么办呢:

class Person
  foo = "world"
  define_method :say do
    puts "hello #{foo}"
  end
end
person = Person.new
person.say #=> "hello world”

注意到了吗? 我们使用了一个define_method方法,加一个block,就可以访问到foo这个变量了。 define_method是Ruby提供的一个内建方法,可以帮助你动态定义方法,当然这个方法是实例方法。这个方法不是重点,重点是block,可以打破作用域的边界,访问到上层的局部变量,所以,这是一个闭包。

再来看一个例子:

class Person
  def say
    word = "hello world"
    lambda{ puts word }
  end
end

person = Person.new
person.say #=> #<Proc:0x007febd4532920@(pry):95 (lambda)>

person.say.call #=> "hello world"

上例中, say方法返回一个lambda对象,这个lambda对象,一直持有方法say里面的一个局部变量 word,当我们想用这个局部变量做些事的时候,只要执行call方法,就可以了。

这就是一个闭包对象。

注:在Ruby1.9之后,lambda也可以写为下面形式:

  lamd = -> (i){puts i}
  lamd.(3) #=> 3

call方法,可以用一个点来代替,不要忘记写那个「.」。

除了lambda对象,Ruby还有一个Proc类:

_proc = Proc.new{|i| puts i} #=> #<Proc:0x007febd4423368@(pry):101>
_proc.call(3) #=> 3

_proc = proc{|i| puts i} #=> #<Proc:0x007febd4423368@(pry):103>

_proc.call(3) #=> 3

至于block、lambda和proc更详细的用法,以及区别,可以参考我这篇博文:大话Ruby block

函数式特性

Ruby的block,实际上也可以当作一个高阶函数看待,高阶函数这个概念,来源于函数式编程范式,函数式语言中,可以把一个函数当作参数传递给另一个函数。

这其实也是Ruby之父的本意,Ruby之父松本行弘深知函数式编程语言的好处, 但是他觉得函数式编程语言太过于晦涩,所以他把block这样设计到Ruby中,可以让函数式编程语言更容易让我等屌丝接受。

所以说,Ruby是有函数式语言特性的面向对象语言一点也不为过。

还有其他的一些特性,也充分说明了Ruby带有函数式编程思想。比如inject方法,完全是函数式编程语言中folds(合并)概念的Ruby实现:

arr = [1, 2, 3, 4, 5]
arr.inject(0) do |sum, i|
  sum += i
end #=> 15

比如,Ruby中的一些方法是成对出现的,比如merge, merge! , sub, sub! 等。

因为函数式语言中有变量不变的特性,所以Ruby实现这些成对的方法,一个没有叹号,一个有叹号,意在说明,有叹号那个会改变变量对象的值,而没有叹号的方法则不会。

Ruby只是多了一个选择,用叹号来警告你。

小结

Ruby的block是非常重要的概念,如果说Ruby的对象模型是Ruby的骨架,那么block就算是Ruby之魂。