块
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之魂。