了解Ruby中的闭包
计算机科学中的闭包是指一段代码,它可以随身携带其创建环境。在Ruby中,闭包包括拥有与范围环境相关的变量的代码块或方法。这对所有的开发者来说都是一个敏感的话题,尤其是那些正在适应函数式范式的开发者。
前提条件
要继续学习本教程,必须具备以下条件。
- 在你的电脑上安装了Ruby。
- 对Ruby编程有基本了解。
- 使用交互式Ruby控制台的一些知识。
闭包
为了清楚地了解什么是闭包,我们需要了解first-class functions ,free variables ,和lexical environment 。
A first-class function 是一个方法,可以被当作一个 ,并作为一个 ,传递给另一个函数。object parameter
A free variable 是不在函数父级范围内声明的,但仍然可以在函数内被访问。
Lexical scoping 是指变量的可见性。 ,也称为 ,是指通过阅读代码来识别程序中的某个变量的能力。Lexical scope eyeball_scoping
在你的交互式控制台中试试这个。
parent_scope = "I'm available everywhere"
3.times do
inner_scope = "Only accessed in the scope above: -"
puts "#{inner_scope} #{parent_scope}}"
end
上面的代码演示了lexical scoping 。inner_scope 只在它被定义的块中可见。当你试图在该块之外访问它时,Ruby会抛出一个异常。
因此,我们可以将closures 定义为以后可以使用的代码块,并在创建它的环境中存储变量。
在确定一个闭包时,我们使用以下规则。
- 它需要是一个函数。
- 函数主体应该引用某个变量。
- 该变量应该在父级范围内声明。
闭包的用例
- 闭包可以被用来模拟 Ruby 中的类。
- 闭包也有助于在Ruby中实现回调。
为了更好地理解我们的主题,让我们来看看Ruby块和可调用对象。
红宝石区块
Blocks 是用来捕获可以接受参数并在以后执行的代码。
在Ruby中,blocks 可以由curly braces 或do/end 关键字对划定。它们也可以作为匿名函数。
让我们来探讨一下yield 关键字和block_given?() 方法。了解这两个概念与闭包的关系是很重要的。
将行为封装到块中并将其传递到方法中是一种强大的编程技术。
yield 当定义在一个 内时,仅仅意味着 。block execute the block
def do_it
yield
end
do_it { puts "I'm doing it" }
当你试图调用没有块的do_it 方法时,控制台会显示一个错误。
我们可以使用block_given?() 方法来捕获exception 。在这种情况下,只有当提供了一个块时,该函数才会被执行。
def do_it
yield if block_given?
end
do_it
闭包和块之间的关系
在Ruby中,blocks ,作为匿名函数。
区块包含局部变量,消除了变量的串通。当一个人给全局变量的名字与块范围内的变量相同时,就会发生这种情况。
x = "Global variable"
1.times { x = "Block variable... conflicts and is modified" }
puts x #Block variable... conflicts and is modified
当你运行上面的片段时,你会得到一个意外的输出。这是因为我们在global scope 中分配了一个与block scope 中相同名称的变量。
我们可以在声明的块中提供一个参数来避免这个问题,如下所示。
x = "Global variable"
1.times { |;x| x = "Block parameter prevents variable overriding" }
puts x #Global variable
Procs
在介绍中,我们讨论了第一类函数。这些方法通常由procs 支持。
Procs是简单的可调用对象。一个块,你可以创建、存储并作为方法参数传递。它也像一个方法一样被执行。
Procs可以使用Proc#call(args),(args)(), 和lambdas 访问。
一个Proc对象在Proc 类的实例化时被创建。
pr = Proc.new { puts "Inside a Proc's block" }
当你使用pr.call 调用上述proc 时,该代码块成为proc 的主体,因此被执行。
Proc与lambdas有很多相似之处。然而,请注意,proc不一定是lambda。
我们使用lambda 关键字创建一个lambda函数,如下图所示。
lambda { |x, y| x + y }.call(x, y)
在Ruby中,lambdas也可以被定义为stabby lambdas。这一点在下面有说明。
->(x, y) { x + y }.call(x, y)
lambda和proc之间的区别
Arity
lambdas,不像proc,期望传递精确数量的参数。
l = lambda { |a, b| puts "x: #{a}, y: #{b}" } # number of args
p = proc { |a, b| puts "x: #{a}, y: #{b}" }
l.call("Ruby", "closures") #invoking object
p.call("Ruby", "closures")
当我们向proc 提供一个参数时,它将不会抛出一个异常,因为对参数没有任何限制。
不像procs,当参数没有正确插入时,lambdas会抛出一个异常。
返回语义
Procs 总是从它们的创建环境中返回,这可能是个问题。
Lambdas比procs更受欢迎,因为它们有和普通方法一样的行为模式。这一点在下面得到证明。
class ReturnSemantic
def method_that_calls_proc_or_lambda(callable_object)
puts "Calling #{proc_or_lambda(callable_object)}"
callable_object.call
puts "#{proc_or_lambda(callable_object)} gets called"
end
def proc_or_lambda(proc_like_thing)
proc_like_thing.lambda? ? "Lambda" : "Proc"
end
end
在上面的例子中,method_that_calls_proc_or_lambda() 负责传递一个可调用对象作为参数。它调用了可调用对象,因此,期望得到返回值。如果可调用对象是proc或lambda,从该方法返回的结果将有所不同。
proc_or_lambda() 使用一个三元操作符来识别传入的参数是proc还是lambda。
c = ReturnSemantic.new
c.method_that_calls_proc_or_lambda lambda { return }
当人们提供一个lambda作为参数时,该方法将返回puts 语句的最后一次执行。
当使用proc时,它会返回一个LocalJumpError ,如下图所示。
c = ReturnSemantic.new
c.method_that_calls_proc_or_lambda proc { return }
结论
闭包在开发者的手中确实很强大。人们可以使用这些组件在Ruby中编写功能性和可读的代码。