我们正在用swag升级FutureStack注册,直到4月30日。条款和条件适用。 现在注册

奇怪的Ruby第4部分:代码吊舱(块,procs和lambdas)

12分钟阅读

这是奇怪的Ruby系列的第4部分。不要错过怪异的Ruby第1部分:结束的开始,奇怪的Ruby第2部分:卓越的保障, 和奇怪的Ruby第3部分:与触发器发挥的乐趣

航天器舱再次,书呆子!欢迎回到奇怪的Ruby系列中的第四部分。这次我们将谈论Ruby的封闭,特别是为什么你想要为您的神奇代码冒险选择特定类型的关闭。

让我们从这个简单的封闭定义开始维基百科:

“在编程语言中,关闭(也是词汇封闭或功能闭合)是与参考环境一起的函数的函数或引用 - 存储对每个非局部变量的参考的表(也称为自由变量或上升)这个功能。关闭 - 与普通功能指针不同 - 即使在其直接词汇范围之外调用,也能够访问这些非本地变量的函数。“

我相信你们中有些人在第一次尝试时就理解了这个解释;不幸的是,我不能把自己算作你们尊敬的会员之一。这可能是一种为了尽可能精确而做出的诚实努力导致描述过于密集的情况。查看闭包的一种更简单的方法是简单地将其视为可移植的代码——很小的代码舱,您可以像传递任何其他对象一样传递并随意执行。

三种封盖

Ruby的封闭件非常重要,我们有3个品种:块,proc和lambdas。

&block proc.new lambda

所有这三个结构都可以称为“封闭”,虽然其中一些不完美地符合技术定义(储存脚,但没有人会打电话给您)。我将逐步贯穿这些中的每一个,并讨论他们的一些差异,让您更好地了解封闭在Ruby中的工作方式,希望我们能找到一些惊喜您的事情。

可以在Ruby中创建一种不同方式的块:

#this是一个街区

{#this是一个街区}

这两个版本的功能完全相同(除非它们不一样,稍后详述),它们甚至共享相同的字节码:

通过在IRB中运行此行生成的字节码:

rubyvm ::指令sequence.compile(“10次; 1337807;结束”)。拆卸

= = disasm: < RubyVM:: InstructionSequence: <编译> @ <编译 >>========== == 抓住表|捕获类型:打破圣:0002艾德:0006 sp: 0000租:0006  |----------------------------------------------------------------------- 0000跟踪1 (1)0002 propertynames 10 0004发送< callinfo !catch table | catch类型:重做st: 0002 ed: 0006 sp: 0000 cont: 0002 | catch类型:next st: 0002 ed: 0006 sp: 0000 cont: 0000 cont:0006年  |----------------------------------------------------------------------- 0000跟踪256(1)0002年跟踪1 0004 propertynames 1337807 0006跟踪512 0008离开

通过在IRB中运行此行生成的字节码:

RubyVM::InstructionSequence.compile(" 10.times{1337807} ").反汇编

= = disasm: < RubyVM:: InstructionSequence: <编译> @ <编译 >>========== == 抓住表|捕获类型:打破圣:0002艾德:0006 sp: 0000租:0006  |----------------------------------------------------------------------- 0000跟踪1 (1)0002 propertynames 10 0004发送< callinfo !catch table | catch类型:重做st: 0002 ed: 0006 sp: 0000 cont: 0002 | catch类型:next st: 0002 ed: 0006 sp: 0000 cont: 0000 cont:0006年  |----------------------------------------------------------------------- 0000跟踪256(1)0002年跟踪1 0004 propertynames 1337807 0006跟踪512 0008离开

在Ruby社区中,多行块体通常包含在a之内做;结尾块和{}语法是为单行机构保留的。这项公约的替代方案是通过迟到提出的吉姆威里奇(我们爱你,吉姆,谢谢你为我们做的一切),也许还有他之前和之后的其他人,但我知道这是吉姆的街区会议。

Jim建议计划使用返回值的块(函数块)应该使用花括号语法,而只产生输出或副作用的块(过程块)应该使用do-end语法。这使您可以一眼就推断出一个块的目的,这听起来当然很有用。

虽然这个约定不像前面提到的多行约定那样经常使用,但我觉得它很吸引人,尽管它最初看起来不那么直观。通常,当我看到一些我觉得不寻常或违反直觉的东西时,我最终会发现这个概念很难看清楚,因为我是在向上看。

正如我之前所示的那样,这两个语法与一个例外相同;他们的优先权。

如果链接了两种方法并且将块传递为参数,则参数将根据语法应用于第一个或第二种方法。

如果block_given,请先def(arg = nil)产量('first')?如果block_given,则结束def second(arg = nil)产量('秒')?结束#该块将首次传递给第一个第一个do | method_name |puts method_name结尾#=> first#此块将传递到第二个第一个第二个{| method_name |puts method_name}#=>第二

如果您要将一系列方法连锁并传递其中一个块,请注意,该块可能不会最终在您认为它的位置。即使您获得此代码要做您想要的代码,您也可以为自己和未来的程序员提供不必要的复杂性;尽你所能减少你添加到世界的愤怒数量并将其分解为多个呼叫,以意图揭示局部变量。

将参数传递给阻止

当您将一个参数传递给一个块时,您将传入的参数包装在其中|是这样的:

启动{|pod| pod。释放}

现在你可以在launch方法中使用一个参数来调用yield,它将作为一个名为pod的参数传递给block:

def发布屈服(pod.new)结束

您可以在调用方法时包含多个参数,或者您可以完全省略参数,因为我们通常使用整数#次:

10.符合“准备启动......”结束

上面的街区实际上有一个参数,是我们循环的柜台,正如您在底层C代码中看到的那样:

for(i = 0; i 

如果您希望引用Integer#次内部的计数器,则可以引用这样的参数:

10.纪念|算筒|put“Preparing to launch in #{10 - count}” end

这就引出了关于块和进程的一个重要观点:参数是可选的。如果你创建了一个期望有参数的proc,并且你在实际调用中忽略了它们,这个proc将正常执行:

pod = proc.new do |导弹,激光器|如果导弹将“激光器充电”如果激光结束,请将“导弹装载”pod.call() = >零

称呼()不输出任何内容,也不会引发错误,即使我们同意传递两个参数,而不是传递none。

Blocks和Procs共享此快速而松散的论点行为以及所有其他行为。块和procs是相同的构造,除了proc是一个实际的Ruby对象,块只是方法调用的语法的一部分。

像块一样,可以以几种方式创建一个proc:

proc .new {surprise nuke}}

这些例子在现代版本的红宝石上有功能相同,但是在1.9之前的Ruby版本中的语法实际上是创建了一个lambda,只是为了确保地球上没有人可能会在他们的头上保持任何直接。如果您偶尔在较旧的Ruby版本中工作,您可能是明智的,以避免旧的语法并只使用proc.new。

正如我前面所展示的,进程并不严格限制参数的数量,块也不严格。相比之下,我们的代码pod的另一种风格,lambda,非常关心数量:

codepod = lambda {|promise| puts " I #{promise}, I 'll never die. "”}codepod。称呼()ArgumentError: wrong number of arguments (0 for 1)

当我们称之为时,我们的代码POD会引发错误而不履行我们承诺提供一个争论。如果我们通过太多参数,它会提高类似的错误:

codepod = lambda {|promise| puts " I #{promise}, I 'll never die. "”}codepod。调用(true, false) ArgumentError: wrong number of arguments (2 for 1)

如果您告诉lambda,它接受特定数量的参数,它希望您遵守这个承诺。这些“接受我想要的任何参数”都不是一种愚蠢的行为。

精明的观众也可能注意到Lambdas与他们的叛逆兄弟姐妹分享了一个课程;lambda.不是Ruby中的一类对象。

C代码再次给我们一个提示这是如何工作的:

proc_new(Value Klass,Int8_t is_lambda)

生成procs和lambdas的所有方法都使用此功能:proc_new.。当您希望您的proc具有lambda行为时,您传递真的proc_new.你得到了一个λ。如果你想要其中一个疯狂的曲折,那么似乎没有关心你所做的,只是通过错误的。的INT8_T.是C程序员如何键入布尔值,因为它们喜欢看1337,它们实际上并不是一个布尔类型。真的错误的在这种情况下实际上是定义为1和0的常数INT8_T.是这些值的单个字节表示。

还有一个非常重要的区别是通过设置引入的is_lambda.真的以下:生成的lambda将从自己的上下文返回。PROC将从周围的上下文返回。

从豆荚返回

最简单的方法是在没有任何环境背景的情况下创建它们:

labamba = lambda {return} labamba.call => nil proctor = proc.new {return} proctor.call => localjumperror:意外返回

proc在创建proc本身的同一上下文中创建了返回,因此在proc中调用返回基本上与键入相同返回独自并试图运行它。

return => localjumperror:意外返回

回报需要朋友,他们需要回归。方法内部返回方法将从该方法返回,因此在该方法内部创建的PROC也将从该方法返回。

def labful_good paladin = proc.new {return} chaotic_evil(圣骑士)把“合法的好!”结束def chaotic_evil(招聘)招聘.Call放“混乱邪恶!”结束合法_代=> nil

合法的货物方法从未在此处完成完成,因此我们从不从任何一种方法打印任何内容。什么时候圣骑士合法的货物它的方法返回永远绑定到从该方法返回。无论在哪里圣骑士然后在世界上旅行它将立即飞回合法的货物方法并执行返回当它被称为。在这个例子中,圣骑士被称为中间chaotic_evil试图转换我们的贵族圣骑士的不成功的方法。的chaotic_evil方法停止执行,所以合法的货物方法,立即返回nil。

在lambda内部返回将会有一个非常不同的结果:

chaotic_evil(圣骑士)将“合法的好!”" end def chaotic_evil(招募)招募。叫放“乱邪!”“结束合法的货物Chaotic evil! Lawful good!

Lambda为其创建了自己的特殊雪花返回,所以打电话圣骑士实际上只会导致lambda从自身返回。在lambda返回空后,下一行chaotic_evil方法宣称,“混沌邪恶!”幸运的是,合法的货物方法也将继续正常执行,我们的高贵圣骑士将再次和优秀的团队一起回来。的合法的货物方法自豪地宣布,“合法的好!”

为了说明进程需要这种类型的返回行为,让我们再看看整数#times方法:

10.纪念|算筒|put“#{count} little monkey(s) jumping on the bed.” end

请记住,此处的Do-End语法正在创建一个块,有效地是proc。循环的每个迭代都会产生该块的计数put我们的字符串,直到我们用完了。

假设我们对第七个猴子有很大的偏见。第七个猴子总是太吵闹,我们担心其他的猴子会受伤。

Def Monkeys!10.纪念|算筒|如果count == 7放出“#{count}小猴子在床上跳。”结束

当我们得到返回值时,我们期望从猴子!在第一个地点开始所有这次跳跃的方法。我们创建的块将返回本地上下文并从那里返回,因此我们将从中返回猴子!,迅速结束我们的恶作剧。

现在让我们假设我们创建的块具有lambda行为。这在Ruby中并不是一个真正的选项,但这就是为什么它是一个选项假装。返回将绑定到我们创建的lambda的上下文,当计数达到7时,我们将简单地从lambda本身返回;我们会从迭代我们的循环。我们将达到七并跳过直接移动到八个。

不仅是对违反直接的行为,它还重复了Ruby中另一个关键字的行为,下一个关键字:

10.纪念|算筒|接下来,如果count == 7把“#{count}小猴子跳在床上。”结尾

这就是为什么block有它们所做的返回行为;你可以从方法返回即使你在循环的中间。有时候你只需要跳出来,就像当你看到第七个猴子过来的时候。

创建闭包

下面的方法都是在Ruby中构造闭包的有效方法:

proc .new {} proc {} lambda {} -> {}

前两个方法创建procs,但是关键字自标记1.9以来,只能以这种方式工作。在那之前,创建了一个兰布,这是一个困惑困惑的人。

第二个示例都创建了lambdas,而最后一个示例可能是最受欢迎的。的- >语法在Ruby 1.9中引入,通常被称为稳定的lambda,这是这种拥抱小码豆荚的相当侵略性的名称。我想我们应该称之为“婴儿火箭”。

调用闭包

有许多方法可以在Ruby中呼叫封闭,其中一些比其他方式更清晰。

Codepod = proc.new {} codepod.call codepod。()codepod [] codepod。[]

这些调用闭包的方法都是有效的,但是我们还是坚持下去吧调用,因为这个话题令人困惑。记住你的代码只能在将来之前变得如此糟糕 - 你被驱动到建造一个时间机器来回来并打败你。

审查

Ruby中有三种闭包类型,但是考虑到块和进程具有类似的行为,您可以将它们视为一种闭包。我们有procs和lambda,根据它们的性质和返回行为来区分:

    • 任意的参数
    • 从家中返回(创建的方法)
  • λ
    • 严格的论点
    • 从自我回报

我们有一些非常好的原因,为什么我们拥有这些不同类型的封闭件,并了解如何在掌握Ruby的道路上有效地对待您的每一个。

如果您有任何疑问或意见,请随时伸出援发:jonan@newrelic.com.。知道我不只是在空白中打字总是很好的。另外,请加入关于这个话题的讨论新遗社区论坛

Proc.new do puts " <3 Jonan " end

Ruby航天器映像礼貌shutterstock.com.