php 闭包里用闭包_了解Ruby闭包

php 闭包里用闭包

Closures are, without a doubt, among Ruby's killer features.

毫无疑问,闭包是Ruby的杀手级功能之一。

Blocks, procs, lambdas, and methods available in Ruby are collectively called closures.

Ruby中可用的块,proc,lambda和方法统称为闭包。

As useful as they can be, their slight differences at times, make you feel they are just different names for the same Ruby construct.

它们虽然有用,但有时会稍有不同,使您感到它们只是同一Ruby构造的不同名称。

However, if you look closely, they have their differences and it takes an eager eye to figure them out.

但是,如果您仔细观察,它们之间会有差异,并且急切需要找出它们。

During the course of this tutorial, we are going to analyse Ruby closures and you will learn:

在本教程的过程中,我们将分析Ruby闭包,您将学到:

  1. What blocks, procs, lambdas, and methods are

    什么是块,proc,lambda和方法
  2. How they differ from eachother

    它们彼此之间有何不同
  3. How you can use blocks, procs, lambdas, and methods in your own code

    如何在自己的代码中使用块,proc,lambda和方法

积木 ( Blocks )

The simplest kind of closure is a block.

最简单的关闭方式是块。

A block is a chunk of code that can be passed to an object/method and is executed under the context of that object.

块是可以传递给对象/方法的代码块,并在该对象的上下文中执行。

If you have worked with arrays, you have probably used an array's each method that allows you to iterate through the array's contents.

如果您使用过数组,则可能使用了数组的each方法,该方法允许您遍历数组的内容。

arr= [1, 2, 3, 4, 5]

arr.each do | element |
  puts element  
end

And here is the output.

这是输出。

1
2
3
4
5

In this case, the code between do and end which is puts element is a block.

在这种情况下, doend之间的代码puts element是一个块。

Though this is a very simple block with a single line of code, blocks can be as long as hundred lines of code depending on what you are trying to achieve using a block.

尽管这是一个只有一行代码的非常简单的块,但是根据您试图使用一个块实现的功能,块可以长达一百行代码。

Here is an example using an array's each_index method.

这是一个使用数组的each_index方法的each_index

arr= [1, 2, 3, 4, 5]

arr.each_index do | index |
  puts "The element at #{index} is #{arr[index]}"
  puts "The square of #{arr[index]} is #{arr[index]**2}"
  puts "The cube of #{arr[index]} is #{arr[index]**3}"
end

The code above produces the output.

上面的代码产生输出。

The element at 0 is 1
The square of 1 is 1
The cube of 1 is 1
The element at 1 is 2
The square of 2 is 4
The cube of 2 is 8
The element at 2 is 3
The square of 3 is 9
The cube of 3 is 27
The element at 3 is 4
The square of 4 is 16
The cube of 4 is 64
The element at 4 is 5
The square of 5 is 25
The cube of 5 is 125

Notice the variables element and index that are enclosed within the | operator to pass into the block in both of our examples.

请注意|中包含的变量elementindex | 在我们的两个示例中,运算符都将传递给该块。

Depending on how the block is written, you can pass one or more variables into your blocks.

根据块的编写方式, 可以将一个或多个变量传递到块中

The delete_if method of the Hash class is an example. It deletes all elements from the hash for which the block evaluates to true.

Hash类的delete_if方法是一个示例。 它从哈希中删除该块评估为true的所有元素。

hash= {a: 1, b: 2, c: 3, d: :d, e: :e, f: :f}

hash = hash.delete_if do | key, value|
  key == value
end

In this case, the hash variable is reduced to {a: 1, b: 2, c: 3}.

在这种情况下, hash变量减小为{a: 1, b: 2, c: 3}

There are a number of object methods in Ruby that accept blocks and execute them under the context of the object they are passed to.

Ruby中有许多对象方法可以接受块并在将其传递给对象的上下文中执行它们。

I highly recommend you read the official Ruby documentation whenever working with any object. It is the best source to learn an object's specifics and will also help you come across methods that accept blocks as a parameter.

我强烈建议您在使用任何对象时都阅读Ruby官方文档 。 它是学习对象详细信息的最佳来源,并且还将帮助您遇到将块作为参数接受的方法。

So, knowing how to pass blocks to the core object methods is all good, but in a way, it limits our possibilities if we do not know how to write our own methods that accept blocks.

因此,知道如何将块传递给核心对象方法都是好的,但是在某种程度上,如果我们不知道如何编写自己的接受块的方法,那么它将限制我们的可能性。

编写接受块的方法 (Writing Methods That Accept Blocks)

From our discussion on blocks so far, you might think that you are about to embark on the most complex parts of this tutorial. But this isn't the case.

从到目前为止对块的讨论中,您可能会认为您将着手本教程中最复杂的部分。 但是事实并非如此。

Before we move to writing our own block-accepting methods, let us write a simple Post class that we can use to build upon throughout this tutorial.

在开始编写自己的块接受方法之前,让我们编写一个简单的Post类,在整个教程中都可以使用该类进行构建。

class Post

  attr_accessor :title, :content, :author, :publish_date

  def initialize(title, content, author, publish_date)
    @title = title
    @content = content
    @author = author
    @publish_date = publish_date
  end

end

We have created a post class with the attributes title, content, author, and publish_date. Using the attr_accessor method, we have made all these four attributes readable and writable. The object contructor which is the initialize method accepts all four of them as a parameter and simply sets the corresponding instance variables.

我们创建了一个具有titlecontentauthorpublish_date属性的post类。 使用attr_accessor方法,我们使这四个属性均可读可写。 作为initialize方法的对象构造initialize全部四个作为参数接受,并仅设置相应的实例变量。

The next thing I want to talk about is the yield keyword.

接下来要谈的是yield关键字。

As simple as it may sound, writing your own block-accepting methods is just a matter of using the yield keyword.

听起来很简单,编写自己的块接受方法只是使用yield关键字的问题。

To demonstrate, I am going to write a block_inspect method on our post class, which will accept a block and pass in the post object's instance variable names and values to the given block.

为了演示,我将在我们的post类上编写一个block_inspect方法,该方法将接受一个块并将post对象的实例变量名称和值传递给给定的块。

class Post

  attr_accessor :title, :content, :author, :publish_date

  def initialize(title, content, author, publish_date)
    @title = title
    @content = content
    @author = author
    @publish_date = publish_date
  end

  def block_inspect
    self.instance_variables.each do |instance_variable|
      stringified_instance_variable_name = instance_variable.to_s.sub('@', '')
      yield(stringified_instance_variable_name, self.instance_variable_get(instance_variable)) 
    end
  end

end

To better understand our block_inspect method, let us first talk about the instance_variables and instance_variable_get methods.

为了更好地理解我们的block_inspect方法,让我们首先讨论一下instance_variablesinstance_variable_get方法。

Both the instance_variables and instance_variable_get methods are part of the core Object class. Since all objects inherit from Object in Ruby, they are automatically available on our post class.

instance_variablesinstance_variable_get方法都是核心Object类的一部分。 由于所有对象都从Ruby中的Object继承,因此它们可以在我们的post类中自动使用。

The instance_variables method returns an array of symbolized instance variable names.

instance_variables方法返回一个带符号的实例变量名称数组。

In case of the post class, it would return [:@title, :@content, :@author, :@publish_date].

如果是post类,它将返回[:@title, :@content, :@author, :@publish_date]

The instance_variable_get method simply returns the value for each of these instance variables. All you have to do is pass in the instance variable name.

instance_variable_get方法仅返回每个这些实例变量的值。 您要做的就是传递实例变量名称。

So, instance_variable_get(:@title) would return the post's title and so on.

因此, instance_variable_get(:@title)将返回帖子的标题,依此类推。

Inside our block_inspect method, we have called self.instance_variables which would return us the array [:@title, :@content, :@author, :@publish_date].

在我们的block_inspect方法内部,我们调用了self.instance_variables ,它将返回给我们数组[:@title, :@content, :@author, :@publish_date]

So basically, our code resolves to [:@title, :@content, :@author, :@publish_date].each which as we saw earlier, allows us to work with each element of this array.

因此,基本上,我们的代码解析为[:@title, :@content, :@author, :@publish_date].each如前所述,每个代码都允许我们使用此数组的每个元素。

Since we want our post attribute names to be human-readable, we need to convert them to strings and remove the prepended @ symbol. This is exactly what the line stringified_instance_variable_name = instance_variable.to_s.sub('@', '') does, saving the resulting string to the variable stringified_instance_variable_name.

由于我们希望我们的post属性名称易于阅读,因此我们需要将它们转换为字符串并删除前置的@符号。 这就是stringified_instance_variable_name = instance_variable.to_s.sub('@', '')所做的,将结果字符串保存到变量stringified_instance_variable_name

The next part yield(stringified_instance_variable_name, self.instance_variable_get(instance_variable)) is pretty simple and interesting.

下一部分yield(stringified_instance_variable_name, self.instance_variable_get(instance_variable))非常简单有趣。

By using the yield keyword, we are simply instructing the compiler to take the block that was passed to this method, and pass it the post instance variable's name and value which are stringified_instance_variable_name, and self.instance_variable_get(instance_variable) respectively. These variables correspond to the variables passed to the block enclosed withing the | operator.

通过使用yield关键字,我们只是指示编译器采用传递给此方法的代码块,然后将实例实例变量的名称和值(分别为stringified_instance_variable_nameself.instance_variable_get(instance_variable)传递给它。 这些变量对应于传递给用|括起来的块的变量| 操作员。

Since we have used the each method, this is done for all the post's instance variables.

由于我们使用了each方法,因此将对所有帖子的实例变量执行此操作。

Let's see it in action.

让我们来看看它的作用。

post= Post.new("Title", "Content", "Author", "Publish_Date")

post.block_inspect do |attribute, value|
  puts "#{attribute} = #{value}"
end

The above code produces the output.

上面的代码产生输出。

title= Title
content = Content
author = Author
publish_date = Publish_Date

Though it may seem our block_inspect method is complete, there is one more thing we can add to it to make it neater.

尽管似乎我们的block_inspect方法是完整的,但我们可以添加另一件事以使其更整洁。

First, let's outline the problem if we leave our block_inspect method as it is.

首先,让我们概述一下如果我们block_inspect方法block_inspect的问题。

post= Post.new("Title", "Content", "Author", "Publish_Date")

post.block_inspect

If you execute the above code, you will be greeted with the no block given (yield) (LocalJumpError) error.

如果执行上面的代码,将会遇到no block given (yield) (LocalJumpError)no block given (yield) (LocalJumpError)错误。

This means our block_inspect method simply assumes a block is passed everytime it is called.

这意味着我们的block_inspect方法仅假设每次调用都传递了一个块。

However, imagine if you are writing a REST API. The LocalJumpError, whenever encountered, would break your whole app for no reason which sounds very silly.

但是,假设您正在编写REST API。 每当遇到LocalJumpError时,都会无故破坏整个应用程序,这听起来很愚蠢。

To remedy that, we simply need to tell our block_inspect method to execute the block if it is given using the block_given? method.

为了解决这个问题,我们只需要告诉我们的block_inspect方法执行该块(如果使用block_given?给出了该block_given? 方法。

Here is our re-written block_inspect method.

这是我们重写的block_inspect方法。

def block_inspect
    self.instance_variables.each do |instance_variable|
      stringified_instance_variable_name = instance_variable.to_s.sub('@', '')
      if block_given?
        yield(stringified_instance_variable_name, self.instance_variable_get(instance_variable))
      end
    end
  end

Or a much cleaner way to do this would be.

或更干净的方法可以做到这一点。

def block_inspect
    self.instance_variables.each do |instance_variable|
      stringified_instance_variable_name = instance_variable.to_s.sub('@', '')
      yield(stringified_instance_variable_name, self.instance_variable_get(instance_variable)) if block_given?          
    end
  end

Now, try the block_inspect method again without passing any block. The LocalJumpError is no longer encountered and everything functions as it should.

现在,再次尝试block_inspect方法而不传递任何块。 不再遇到LocalJumpError ,一切正常。

Apart from yield, we can use a block's call method to write block-accepting functions but by using it, we have to explicitly declare in our method definition that a block will be passed to it.

除了yield之外,我们可以使用块的call方法来编写接受块的函数,但是通过使用它,我们必须在方法定义中明确声明将块传递给它。

Let's edit the block_inspect method once again to understand how that is done.

让我们再次编辑block_inspect方法以了解它是如何完成的。

def block_inspect(&block)
  self.instance_variables.each do |instance_variable|
    stringified_instance_variable_name = instance_variable.to_s.sub('@', '')
    block.call(stringified_instance_variable_name, self.instance_variable_get(instance_variable)) if block_given?          
  end
end

Starting with the method signature, we have explicitly added block to the parameters list. The only change apart from that is calling the block using block.call and passing in the instance variable names and values so that they are available inside our block.

从方法签名开始,我们已将block显式添加到参数列表中。 唯一的变化是使用block.call调用该块,并传入实例变量的名称和值,以便它们在我们的块内可用。

Also, notice that we have added & before block when specifying the function parameters. It means that blocks need to be passed by reference, and not value.

另外,请注意,在指定功能参数时,我们在&之前添加了& block 。 这意味着需要通过引用而不是值来传递块。

程序 ( Procs )

The simplest way to understand procs (short for procedures) is when you save your blocks to a variable, it is called a proc.

理解proc的最简单方法(过程的缩写)是将块保存到变量中时,称为proc。

In other words, a block is actually a proc, only that it has been declared in-line and not saved to a variable.

换句话说,一个块实际上是一个proc,只是它已被内联声明并且没有保存到变量中。

Here is a proof of concept.

这是概念证明。

Let us write a very simple function that accepts a block as a parameter. We will only be echoing the class of the passed block inside the function.

让我们编写一个非常简单的函数,该函数接受块作为参数。 我们只会在函数内部回显所传递块的类。

def show_class(&block)
  puts "The block class is #{block.class}" if block_given?
  yield if block_given?
end

show_class do
  puts "Hi! from inside the block"
end

The above code produces the output.

上面的代码产生输出。

The block class is Proc
Hi! from inside the block

Voila!

瞧!

Let us add another method to our post class to do the same inspection using a proc.

让我们在post类中添加另一个方法,以使用proc进行相同的检查。

def proc_inspect(block)
    self.instance_variables.each do |instance_variable|
      stringified_instance_variable_name = instance_variable.to_s.sub('@', '')
      block.call(stringified_instance_variable_name, self.instance_variable_get(instance_variable))
    end
  end

Our proc_inspect method seems pretty similar to the block_inspect method, the only changes we have made is to remove the & before the function parameter and the block_given? conditional.

我们proc_inspect方法看起来很相似block_inspect方法,唯一的变化,我们已经取得的去除&功能参数和之前block_given? 有条件的。

Now, our proc_inspect method behaves just like any other function and will be able to figure out the number of parameters needed.

现在,我们的proc_inspect方法的行为与任何其他函数一样,并且能够找出所需的参数数量。

If a proc is not passed to the function, there will be a wrong number of arguments error instead of a LocalJumpError.

如果没有将proc传递给该函数,则将出现wrong number of arguments错误,而不是LocalJumpError

Here is the proc_inspect method in action.

这是正在使用的proc_inspect方法。

proc_inspect= Proc.new do |attribute, value|
  puts "#{attribute} = #{value}"
end  

post = Post.new("Title", "Content", "Author", "Publish_Date")

post.proc_inspect(proc_inspect)

The above code produces the same exact output as before.

上面的代码产生与以前相同的精确输出。

As mentioned earlier, procs are blocks that can be saved to a variable so that is exactly what is happening in the first line of code.

如前所述, proc是可以保存到变量的块,因此正好在第一行代码中。

The next two lines simply create a post object and pass it the proc object using the proc_inspect method.

接下来的两行只需创建一个post对象,然后使用proc_inspect方法将proc对象传递给该对象。

Procs can also make your code re-usable.

Procs还可以使您的代码可重复使用。

Imagine if you wrote a 100-line block that goes through an object, detailing it's various specifics such as the attribute names and values and the memory each of them consumes.

想象一下,如果您编写了一个贯穿对象的100行块,详细说明了它的各种细节,例如属性名称和值以及它们各自消耗的内存。

In such a case, writing a 100-line block in-line everywhere it needs to be used sounds very untidy and impractical.

在这种情况下,在需要使用的任何地方在线编写一个100行的块听起来很麻烦,也不切实际。

For such scenarios and similar, you can create a proc and simply call it wherever needed.

对于此类情况和类似情况,您可以创建一个proc并在需要时直接调用它。

Lambdas ( Lambdas )

Lambdas will seem very similar to procs but before we talk about their differences, let us see their practical implementation.

Lambda看起来与proc非常相似,但是在我们讨论它们之间的差异之前,让我们看看它们的实际实现。

Add another method lambda_inspect to the post class.

将另一个方法lambda_inspect添加到post类。

def lambda_inspect(lambda)
  self.instance_variables.each do |instance_variable|
    stringified_instance_variable_name = instance_variable.to_s.sub('@', '')
    lambda.call(stringified_instance_variable_name, self.instance_variable_get(instance_variable))
  end
end

It is exactly like the proc_inspect method and even calling it will not outline any differences except for a few.

它与proc_inspect方法完全一样,甚至调用它也不会概述任何差异,只有一些差异。

lambda_inspect= lambda do |attribute, value|
  puts "#{attribute} = #{value}"
end

post.lambda_inspect(lambda_inspect)

So what are the differences?

那有什么区别呢?

Well, two.

好吧,两个。

The first one is a proc does not report an error if the number of parameters passed does not match the number of parameters it accepts whereas a lambda does.

第一个是proc,如果传递的参数数量与它接受的参数数量不匹配,而lambda则不报告错误

Let me show you.

让我给你演示。

proc_inspect= Proc.new do |attribute, value, answer_to_life_and_universe|
  puts "#{attribute} = #{value}"
  puts "Answer to life and universe is #{answer_to_life_and_universe.class}"
end

post = Post.new("Title", "Content", "Author", "Publish_Date")

post.proc_inspect(proc_inspect)

And here is the output.

这是输出。

title= Title
Answer to life and universe is NilClass
content = Content
Answer to life and universe is NilClass
author = Author
Answer to life and universe is NilClass
publish_date = Publish_Date
Answer to life and universe is NilClass

As you can see, the class of answer_to_life_and_universe is printed NilClass for all the iterations. We could also have printed the variable answer_to_life_and_universe using puts answer_to_life_and_universe but since it is NIL, nothing is printed out.

如您所见,在所有迭代中, answer_to_life_and_universe的类均answer_to_life_and_universeNilClass 。 我们也可以使用puts answer_to_life_and_universe answer_to_life_and_universe来打印变量answer_to_life_and_universe但是由于它是NIL ,因此不会打印任何内容。

Now, let us do the same thing using a lambda.

现在,让我们使用lambda做同样的事情。

lambda_inspect= lambda do |attribute, value, answer_to_life_and_universe|
  puts "#{attribute} = #{value}"
  puts "Answer to life and universe is #{answer_to_life_and_universe.class}"
end

post = Post.new("Title", "Content", "Author", "Publish_Date")

post.lambda_inspect(lambda_inspect)

You will be greeted with the wrong number of arguments (given 2, expected 3) error and the compiler will be all over you like an angry girlfriend.

您将收到wrong number of arguments (given 2, expected 3) ,并且编译器将像生气的女友一样遍布您。

The second difference has to do with the return keyword.

第二个区别与return关键字有关。

When you use return inside a proc (which is inside a function), it behaves as though using return inside the function, halting further execution and exiting the function with the returned value.

当您使用return一个进程内(这是一个函数内),它的行为就好像使用return的功能里面,停止进一步的执行和退出有返回值的函数。

Whereas in case of lambdas, only the value is returned and the function resumes execution as normal.

而对于lambda,则仅返回值,并且该函数将照常恢复执行。

As you know by now, I love to show things using code.

如您现在所知,我喜欢使用代码展示事物。

def proc_return
  Proc.new do 
    return "Inside proc !!!"
  end.call
  return "Inside proc_return !!!"
end

def lambda_return
  lambda do
    return "Inside lambda_inspect !!!"
  end.call
  return "Inside lambda_return !!!"  
end

puts proc_return
puts lambda_return

Which produces the output.

产生输出。

Inside proc!!!
Inside lambda_return !!!

方法 ( Methods )

Functions and object methods can be collectively called methods.

函数和对象方法可以统称为方法。

There is not much to discuss regarding the specifics of methods.

关于方法的细节,没有太多讨论。

However, with respect to closures, it is worth mentioning that (tongue twister alert!) the method method makes methods usable in place of blocks, procs, and lambdas.

但是,对于闭包,值得一提的是(舌头扭曲警报!)方法method使方法可用于代替块,proc和lambda。

def print_object_property(attribute, value)
  puts "#{attribute} = #{value}"
end

post = Post.new("Title", "Content", "Author", "Publish_Date")

post.block_inspect(&(method(:print_object_property)))
puts "=========================="
post.proc_inspect(method(:print_object_property))
puts "=========================="
post.lambda_inspect(method(:print_object_property))

The output of the above code shows our function is used in place of block, proc, and lambda flawlessly.

上面代码的输出表明,我们的函数完美地替代了block,proc和lambda。

title= Title
content = Content
author = Author
publish_date = Publish_Date
==========================
title = Title
content = Content
author = Author
publish_date = Publish_Date
==========================
title = Title
content = Content
author = Author
publish_date = Publish_Date

那就是所有的人! ( That's All Folks! )

I hope you enjoyed my dissection of Ruby closures and that it has opened up the language's new horizons unto you.

我希望您喜欢我对Ruby闭包的剖析,它为您打开了该语言的新视野。

Understanding closures is a big leap for any Rubyist, be it a beginner or a more seasoned developer, so pat yourself on the back.

对于任何Rubyist而言,无论是初学者还是经验丰富的开发人员,了解闭包都是一个巨大的飞跃,所以请反击自己。

Though it will take you some considerable amount of experience to fully understand the best use-cases for them.

尽管您将需要大量的经验来完全了解针对他们的最佳用例。

I hope you found this tutorial interesting and knowledgeable. Until my next piece, happy coding!

我希望您发现本教程有趣且知识丰富。 直到我的下一篇文章,祝您编程愉快!

翻译自: https://scotch.io/tutorials/understanding-ruby-closures

php 闭包里用闭包

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值