Ruby 中的块和收益
我试图了解块和产量以及它们在 Ruby 中的工作原理。
如何使用yield
?我研究过的许多 Rails 应用程序都以一种奇怪的方式使用 yield
。
有人可以向我解释或告诉我去哪里理解它们吗?
I am trying to understand blocks and yield
and how they work in Ruby.
How is yield
used? Many of the Rails applications I've looked at use yield
in a weird way.
Can someone explain to me or show me where to go to understand them?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(10)
是的,一开始有点令人困惑。
在 Ruby 中,方法可以接收代码块以执行任意代码段。
当方法需要块时,您可以通过调用
yield
函数来调用它。示例:
以
Person
为例,它是一个具有name
属性和do_with_name
方法的类。当调用该方法时,它将把name
属性传递给块。现在您可以调用此方法并传递任意代码块。
将打印:
请注意,该块接收一个名为
value
的变量作为参数。当代码调用yield
时,它会将@name
的值作为参数传递。可以使用不同的块调用相同的方法。
例如反转名称:
其他更有趣的现实生活示例:
过滤数组中的元素:
或按名称长度排序:
如果块是可选的,则可以使用:
如果不是可选的,则只需调用它。
您可以使用
irb
在计算机上尝试这些示例(交互式 Ruby Shell )以下是复制/粘贴就绪形式的所有示例:
Yes, it is a bit puzzling at first.
In Ruby, methods can receive a code block in order to perform arbitrary segments of code.
When a method expects a block, you can invoke it by calling the
yield
function.Example:
Take
Person
, a class with aname
attribute and ado_with_name
method. When the method is invoked it will pass thename
attribute to the block.Now you can invoke this method and pass an arbitrary code block.
Would print:
Notice the block receives as a parameter a variable called
value
. When the code invokesyield
it passes as argument the value of@name
.The same method can be invoked with a different block.
For instance to reverse the name:
Other more interesting real life examples:
Filter elements in an array:
Or sort by name length:
If the block is optional you can use:
If is not optional, just invoke it.
You can try these examples on your computer with
irb
(Interactive Ruby Shell)Here are all the examples in a copy/paste ready form:
在 Ruby 中,方法可以检查它们的调用方式是否除了正常参数之外还提供了块。通常,这是使用
block_given?
方法完成的,但您也可以通过在最终参数名称之前添加前缀 & 符号 (&
) 来将该块引用为显式 Proc。如果使用块调用方法,则如果需要,该方法可以使用一些参数将控制权交给块(调用块)。考虑这个示例方法,它演示了:
或者,使用特殊的块参数语法:
In Ruby, methods can check to see if they were called in such a way that a block was provided in addition to the normal arguments. Typically this is done using the
block_given?
method but you can also refer to the block as an explicit Proc by prefixing an ampersand (&
) before the final argument name.If a method is invoked with a block then the method can
yield
control to the block (call the block) with some arguments, if needed. Consider this example method that demonstrates:Or, using the special block argument syntax:
很可能有人会在这里提供真正详细的答案,但我总是发现 这篇来自 Robert Sosinski 的文章 很好地解释了块、过程和表达式之间的微妙之处。拉姆达。
我应该补充一点,我相信我链接的帖子是特定于 ruby 1.8 的。 ruby 1.9 中发生了一些变化,例如块变量是块的本地变量。在 1.8 中,您会得到类似以下内容的信息:
而 1.9 则会显示:
我这台机器上没有 1.9,因此上面的内容可能有错误。
It's quite possible that someone will provide a truly detailed answer here, but I've always found this post from Robert Sosinski to be a great explanation of the subtleties between blocks, procs & lambdas.
I should add that I believe the post I'm linking to is specific to ruby 1.8. Some things have changed in ruby 1.9, such as block variables being local to the block. In 1.8, you'd get something like the following:
Whereas 1.9 would give you:
I don't have 1.9 on this machine so the above might have an error in it.
我发现这篇文章非常有用。特别是下面的示例:
应该给出以下输出:
因此,每次调用
yield
时,ruby 都会运行do
块中或do
块中的代码。代码>{}。如果为yield
提供了参数,则该参数将作为参数提供给do
块。对我来说,这是我第一次真正理解
do
块的作用。它基本上是函数访问内部数据结构的一种方式,无论是迭代还是函数配置。因此,在 Rails 中,您可以编写以下内容:
这将运行
respond_to
函数,该函数生成带有(内部)format
参数的do
块。然后,您可以在此内部变量上调用.html
函数,该函数又会生成用于运行render
命令的代码块。请注意,.html
仅当它是所请求的文件格式时才会产生。 (技术性:这些函数实际上使用block.call
而不是yield
,正如您从 source 但功能本质上是相同的,请参阅或者换句话说,它类似于一个函数以匿名函数作为参数,然后在 javascript 中调用它。
I found this article to be very useful. In particular, the following example:
which should give the following output:
So essentially each time a call is made to
yield
ruby will run the code in thedo
block or inside{}
. If a parameter is provided toyield
then this will be provided as a parameter to thedo
block.For me, this was the first time that I understood really what the
do
blocks were doing. It is basically a way for the function to give access to internal data structures, be that for iteration or for configuration of the function.So when in rails you write the following:
This will run the
respond_to
function which yields thedo
block with the (internal)format
parameter. You then call the.html
function on this internal variable which in turn yields the code block to run therender
command. Note that.html
will only yield if it is the file format requested. (technicality: these functions actually useblock.call
notyield
as you can see from the source but the functionality is essentially the same, see this question for a discussion.) This provides a way for the function to perform some initialisation then take input from the calling code and then carry on processing if required.Or put another way, it's similar to a function taking an anonymous function as an argument and then calling it in javascript.
我想在已经很好的答案中添加为什么你会这样做。
不知道你来自什么语言,但假设它是静态语言,这种事情看起来很熟悉。这就是你在java中读取文件的方式
忽略整个流链接的事情,这个想法是
这与你在ruby中的做法
截然不同。分解这一点,
在这里,您基本上不用处理第一步和第二步,而是将其委托给另一堂课。正如您所看到的,这极大地减少了您必须编写的代码量,从而使代码更易于阅读,并减少了内存泄漏或文件锁未清除等问题的可能性。
现在,并不是说你不能在 java 中做类似的事情,事实上,人们已经这样做了几十年了。它称为策略模式。不同之处在于,如果没有块,对于像文件示例这样的简单内容,由于需要编写的类和方法的数量,策略就变得矫枉过正。使用块,这是一种简单而优雅的方法,不以这种方式构建代码是没有任何意义的。
这不是使用块的唯一方式,但其他方式(例如 Builder 模式,您可以在 Rails 中的 form_for api 中看到)非常相似,一旦您了解了这一点,就会很明显发生了什么。当您看到块时,通常可以安全地假设该方法调用就是您想要执行的操作,并且该块正在描述您想要如何执行它。
I wanted to sort of add why you would do things that way to the already great answers.
No idea what language you are coming from, but assuming it is a static language, this sort of thing will look familiar. This is how you read a file in java
Ignoring the whole stream chaining thing, The idea is this
This is how you do it in ruby
Wildly different. Breaking this one down
Here, instead of handling step one and two, you basically delegate that off into another class. As you can see, that dramatically brings down the amount of code you have to write, which makes things easier to read, and reduces the chances of things like memory leaks, or file locks not getting cleared.
Now, its not like you can't do something similar in java, in fact, people have been doing it for decades now. It's called the Strategy pattern. The difference is that without blocks, for something simple like the file example, strategy becomes overkill due to the amount of classes and methods you need to write. With blocks, it is such a simple and elegant way of doing it, that it doesn't make any sense NOT to structure your code that way.
This isn't the only way blocks are used, but the others (like the Builder pattern, which you can see in the form_for api in rails) are similar enough that it should be obvious whats going on once you wrap your head around this. When you see blocks, its usually safe to assume that the method call is what you want to do, and the block is describing how you want to do it.
在 Ruby 中,块基本上是可以传递给任何方法并由任何方法执行的代码块。块始终与方法一起使用,方法通常向块提供数据(作为参数)。
块广泛用于 Ruby gem(包括 Rails)和编写良好的 Ruby 代码中。它们不是对象,因此不能分配给变量。
基本语法
块是由 { } 或 do..end 括起来的一段代码。按照约定,大括号语法应用于单行块,而 do..end 语法应用于多行块。
任何方法都可以接收块作为隐式参数。块由方法内的yield 语句执行。基本语法是:
当到达yield语句时,meditate方法将控制权交给块,执行块内的代码并将控制权返回给方法,该方法在yield语句之后立即恢复执行。
当一个方法包含yield语句时,它期望在调用时接收一个块。如果未提供块,则一旦到达yield 语句就会抛出异常。我们可以使该块可选并避免引发异常:
不可能将多个块传递给一个方法。每种方法只能接收一个块。
查看更多信息:http://www.zenruby.info /2016/04/introduction-to-blocks-in-ruby.html
In Ruby, a block is basically a chunk of code that can be passed to and executed by any method. Blocks are always used with methods, which usually feed data to them (as arguments).
Blocks are widely used in Ruby gems (including Rails) and in well-written Ruby code. They are not objects, hence cannot be assigned to variables.
Basic Syntax
A block is a piece of code enclosed by { } or do..end. By convention, the curly brace syntax should be used for single-line blocks and the do..end syntax should be used for multi-line blocks.
Any method can receive a block as an implicit argument. A block is executed by the yield statement within a method. The basic syntax is:
When the yield statement is reached, the meditate method yields control to the block, the code within the block is executed and control is returned to the method, which resumes execution immediately following the yield statement.
When a method contains a yield statement, it is expecting to receive a block at calling time. If a block is not provided, an exception will be thrown once the yield statement is reached. We can make the block optional and avoid an exception from being raised:
It is not possible to pass multiple blocks to a method. Each method can receive only one block.
See more at: http://www.zenruby.info/2016/04/introduction-to-blocks-in-ruby.html
我有时会这样使用“yield”:
I sometimes use "yield" like this:
简单来说,Yields 允许您创建的方法接受和调用块。明确来说,yield 关键字是块中“内容”将被执行的位置。
Yields, to put it simply, allow the method you create to take and call blocks. The yield keyword specifically is the spot where the 'stuff' in the block will be performed.
关于yield,我想在这里提出两点。首先,虽然这里的很多答案都讨论了将块传递给使用yield的方法的不同方法,但我们也讨论一下控制流。这尤其重要,因为您可以多次生成一个块。我们来看一个例子:
当调用each方法时,它会逐行执行。现在,当我们到达 3.times 块时,该块将被调用 3 次。每次它调用yield。该yield 链接到与调用each 方法的方法关联的块。值得注意的是,每次调用yield 时,它都会将控制权返回给客户端代码中的each 方法的块。一旦该块执行完毕,它就会返回到 3.times 块。并且这种情况发生了 3 次。因此,客户端代码中的该块会在 3 个不同的场合被调用,因为 Yield 被显式调用了 3 次。
我的第二点是关于 enum_for 和 Yield。 enum_for 实例化 Enumerator 类,并且此 Enumerator 对象也响应yield。
因此请注意,每次我们使用外部迭代器调用种类时,它只会调用一次yield。下次我们调用它时,它将调用下一个yield,依此类推。
关于 enum_for 有一个有趣的花絮。在线文档声明如下:
如果您没有指定符号作为 enum_for 的参数,ruby 会将枚举器挂接到接收者的each 方法。有些类没有each 方法,例如String 类。
因此,对于使用 enum_for 调用某些对象的情况,您必须明确您的枚举方法是什么。
There are two points I want to make about yield here. First, while a lot of answers here talk about different ways to pass a block to a method which uses yield, let's also talk about the control flow. This is especially relevant since you can yield MULTIPLE times to a block. Let's take a look at an example:
When the each method is invoked, it executes line by line. Now when we get to the 3.times block, this block will be invoked 3 times. Each time it invokes yield. That yield is linked to the block associated with the method that called the each method. It is important to notice that each time yield is invoked, it returns control back to the block of the each method in client code. Once the block is finished executing, it returns back to the 3.times block. And this happens 3 times. So that block in client code is invoked on 3 separate occasions since yield is explicitly called 3 separate times.
My second point is about enum_for and yield. enum_for instantiates the Enumerator class and this Enumerator object also responds to yield.
So notice every time we invoke kinds with the external iterator, it will invoke yield only once. The next time we call it, it will invoke the next yield and so on.
There's an interesting tidbit with regards to enum_for. The documentation online states the following:
If you do not specify a symbol as an argument to enum_for, ruby will hook the enumerator to the receiver's each method. Some classes do not have an each method, like the String class.
Thus, in the case of some objects invoked with enum_for, you must be explicit as to what your enumerating method will be.
Yield 可以用作无名块以在方法中返回值。考虑以下代码:
您可以创建一个方法“Up”,并为其分配一个参数。您现在可以将此参数分配给yield,它将调用并执行关联的块。您可以在参数列表之后分配块。
当Up方法调用yield时,带有一个参数,它被传递给块变量来处理请求。
Yield can be used as nameless block to return a value in the method. Consider the following code:
You can create a method "Up" which is assigned one argument. You can now assign this argument to yield which will call and execute an associated block. You can assign the block after the parameter list.
When the Up method calls yield, with an argument, it is passed to the block variable to process the request.