我正在尝试理解块和yield
以及它们如何在Ruby中工作。
yield
如何使用?我看过的很多Rails应用程序都以奇怪的方式使用yield
。
有人可以向我解释或告诉我去哪里理解它们吗?
答案 0 :(得分:363)
是的,一开始有点令人费解。
在Ruby中,方法可能会收到一个代码块,以便执行任意代码段。
当一个方法需要一个块时,它会通过调用yield
函数来调用它。
例如,迭代列表或提供自定义算法非常方便。
采用以下示例:
我将定义一个用名称初始化的Person
类,并提供一个do_with_name
方法,在调用时,只会将name
属性传递给接收到的块。
class Person
def initialize( name )
@name = name
end
def do_with_name
yield( @name )
end
end
这将允许我们调用该方法并传递任意代码块。
例如,要打印我们要做的名字:
person = Person.new("Oscar")
#invoking the method passing a block
person.do_with_name do |name|
puts "Hey, his name is #{name}"
end
会打印:
Hey, his name is Oscar
注意,块接收一个名为name
的变量作为参数(N.B.你可以随意调用这个变量,但是把它称为name
是有意义的)。当代码调用yield
时,它会使用@name
的值填充此参数。
yield( @name )
我们可以提供另一个块来执行不同的操作。例如,反转名称:
#variable to hold the name reversed
reversed_name = ""
#invoke the method passing a different block
person.do_with_name do |name|
reversed_name = name.reverse
end
puts reversed_name
=> "racsO"
我们使用完全相同的方法(do_with_name
) - 它只是一个不同的块。
这个例子很简单。更有趣的用法是过滤数组中的所有元素:
days = ["monday", "tuesday", "wednesday", "thursday", "friday"]
# select those which start with 't'
days.select do | item |
item.match /^t/
end
=> ["tuesday", "thursday"]
或者,我们也可以提供自定义排序算法,例如基于字符串大小:
days.sort do |x,y|
x.size <=> y.size
end
=> ["monday", "friday", "tuesday", "thursday", "wednesday"]
我希望这可以帮助您更好地理解它。
顺便说一句,如果该块是可选的,您应该将其称为:
yield(value) if block_given?
如果不是可选的,只需调用它即可。
答案 1 :(得分:22)
在Ruby中,方法可以检查它们是否以除了普通参数之外还提供块的方式调用。通常,这是使用block_given?
方法完成的,但您也可以通过在最终参数名称前添加&符号(&
)作为显式Proc来引用该块。
如果使用块调用方法,则方法可以yield
使用某些参数控制块(调用块)(如果需要)。请考虑以下示例方法:
def foo(x)
puts "OK: called as foo(#{x.inspect})"
yield("A gift from foo!") if block_given?
end
foo(10)
# OK: called as foo(10)
foo(123) {|y| puts "BLOCK: #{y} How nice =)"}
# OK: called as foo(123)
# BLOCK: A gift from foo! How nice =)
或者,使用特殊块参数语法:
def bar(x, &block)
puts "OK: called as bar(#{x.inspect})"
block.call("A gift from bar!") if block
end
bar(10)
# OK: called as bar(10)
bar(123) {|y| puts "BLOCK: #{y} How nice =)"}
# OK: called as bar(123)
# BLOCK: A gift from bar! How nice =)
答案 2 :(得分:22)
很可能有人会在这里提供一个真正详细的答案,但我总是发现罗伯特索辛斯基this post是对块,触发器和触发器之间微妙之处的一个很好的解释。 lambda表达式。
我应该补充一点,我相信我链接的帖子特定于ruby 1.8。 ruby 1.9中的一些内容已经发生了变化,例如块变量是块的本地变量。在1.8中,您将获得以下内容:
>> a = "Hello"
=> "Hello"
>> 1.times { |a| a = "Goodbye" }
=> 1
>> a
=> "Goodbye"
而1.9会给你:
>> a = "Hello"
=> "Hello"
>> 1.times { |a| a = "Goodbye" }
=> 1
>> a
=> "Hello"
我在这台机器上没有1.9,所以上面可能有错误。
答案 3 :(得分:13)
我想补充一下为什么你会按照已经很好的答案做事。
不知道你来自哪种语言,但假设它是一种静态语言,这种事情看起来很熟悉。这是你在java中读取文件的方式
public class FileInput {
public static void main(String[] args) {
File file = new File("C:\\MyFile.txt");
FileInputStream fis = null;
BufferedInputStream bis = null;
DataInputStream dis = null;
try {
fis = new FileInputStream(file);
// Here BufferedInputStream is added for fast reading.
bis = new BufferedInputStream(fis);
dis = new DataInputStream(bis);
// dis.available() returns 0 if the file does not have more lines.
while (dis.available() != 0) {
// this statement reads the line from the file and print it to
// the console.
System.out.println(dis.readLine());
}
// dispose all the resources after using them.
fis.close();
bis.close();
dis.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
忽略整个流链接的事情,这个想法就是这个
这是你在ruby中的方法
File.open("readfile.rb", "r") do |infile|
while (line = infile.gets)
puts "#{counter}: #{line}"
counter = counter + 1
end
end
完全不同。打破这一个
这里,您基本上将其委托给另一个类,而不是处理第一步和第二步。正如您所看到的,这会大大降低您必须编写的代码量,从而使事情更容易阅读,并减少内存泄漏或文件锁定无法清除的可能性。
现在,它不像你不能在java中做类似的东西,事实上,人们已经做了几十年了。它被称为Strategy模式。区别在于没有块,对于像文件示例这样简单的东西,由于需要编写的类和方法的数量,策略变得过度。使用块,它是这样一种简单而优雅的方式,没有任何意义,不会以这种方式构建代码。
这不是使用块的唯一方法,但是其他的(比如Builder模式,你可以在rails中的form_for api中看到它)足够相似,一旦你包裹你的脑袋,它应该是显而易见的围绕这个。当你看到块时,通常可以安全地假设方法调用是你想要做的,而块正在描述你想要怎么做。
答案 4 :(得分:12)
我发现this article非常有用。特别是,以下示例:
#!/usr/bin/ruby
def test
yield 5
puts "You are in the method test"
yield 100
end
test {|i| puts "You are in the block #{i}"}
test do |i|
puts "You are in the block #{i}"
end
应该提供以下输出:
You are in the block 5
You are in the method test
You are in the block 100
You are in the block 5
You are in the method test
You are in the block 100
所以基本上每次调用yield
时,ruby都会运行do
块或{}
内的代码。如果向yield
提供了参数,那么这将作为参数提供给do
块。
对我而言,这是我第一次真正理解do
块正在做什么。它基本上是一种允许函数访问内部数据结构的方法,用于迭代或配置函数。
所以当你在rails中写下以下内容时:
respond_to do |format|
format.html { render template: "my/view", layout: 'my_layout' }
end
这将运行respond_to
函数,该函数产生带有(内部)do
参数的format
块。然后在此内部变量上调用.html
函数,然后生成代码块以运行render
命令。请注意,.html
只有在请求的文件格式时才会生成。 (技术性:这些函数实际上使用block.call
而不是yield
,您可以从source看到,但功能基本相同,请参阅this question进行讨论。)这提供了函数执行某些初始化的方法,然后从调用代码中获取输入,然后在需要时继续处理。
或换句话说,它类似于将匿名函数作为参数然后在javascript中调用它的函数。
答案 5 :(得分:7)
在Ruby中,块基本上是一块代码,可以传递给任何方法并由其执行。块总是与方法一起使用,这些方法通常将数据提供给它们(作为参数)。
块广泛用于Ruby gem(包括Rails)和编写良好的Ruby代码中。它们不是对象,因此不能分配给变量。
块是由{}或do..end包围的一段代码。按照惯例,大括号语法应该用于单行块,而do..end语法应该用于多行块。
{ # This is a single line block }
do
# This is a multi-line block
end
任何方法都可以接收块作为隐式参数。块由一个方法中的yield语句执行。基本语法是:
def meditate
print "Today we will practice zazen"
yield # This indicates the method is expecting a block
end
# We are passing a block as an argument to the meditate method
meditate { print " for 40 minutes." }
Output:
Today we will practice zazen for 40 minutes.
当达到yield语句时,meditate方法产生对块的控制,执行块内的代码并将控制返回给方法,该方法在yield语句之后立即恢复执行。
当方法包含yield语句时,它期望在调用时接收块。如果未提供块,则在达到yield语句后将抛出异常。我们可以使块可选,并避免引发异常:
def meditate
puts "Today we will practice zazen."
yield if block_given?
end meditate
Output:
Today we will practice zazen.
无法将多个块传递给方法。每种方法只能接收一个块。
详情请见:http://www.zenruby.info/2016/04/introduction-to-blocks-in-ruby.html
答案 6 :(得分:5)
我有时会像这样使用“收益率”:
def add_to_http
"http://#{yield}"
end
puts add_to_http { "www.example.com" }
puts add_to_http { "www.victim.com"}
答案 7 :(得分:4)
简单地说,收益率允许您创建的方法获取和调用块。 yield关键字专门用于执行块中“stuff”的位置。
答案 8 :(得分:1)
关于产量,我想提出两点。首先,尽管这里有很多答案都讨论了将块传递给使用yield的方法的不同方法,但我们还讨论了控制流。这一点特别重要,因为您可以将一个块产生多次。让我们看一个例子:
class Fruit
attr_accessor :kinds
def initialize
@kinds = %w(orange apple pear banana)
end
def each
puts 'inside each'
3.times { yield (@kinds.tap {|kinds| puts "selecting from #{kinds}"} ).sample }
end
end
f = Fruit.new
f.each do |kind|
puts 'inside block'
end
=> inside each
=> selecting from ["orange", "apple", "pear", "banana"]
=> inside block
=> selecting from ["orange", "apple", "pear", "banana"]
=> inside block
=> selecting from ["orange", "apple", "pear", "banana"]
=> inside block
调用每个方法时,它将逐行执行。现在,当我们到达3.times块时,该块将被调用3次。每次调用yield。该产量链接到与调用each方法的方法相关联的块。重要的是要注意,每次调用yield时,它将控制权返回给客户端代码中每个方法的块。一旦该块完成执行,它将返回到3.times块。这会发生3次。因此,在3个不同的情况下调用客户端代码中的该块,因为yield明确地称为3个不同的时间。
我的第二点是关于enum_for和yield的。 enum_for实例化Enumerator类,并且此Enumerator对象也响应yield。
class Fruit
def initialize
@kinds = %w(orange apple)
end
def kinds
yield @kinds.shift
yield @kinds.shift
end
end
f = Fruit.new
enum = f.to_enum(:kinds)
enum.next
=> "orange"
enum.next
=> "apple"
因此请注意,每次我们使用外部迭代器调用种类时,它只会调用yield一次。下次调用它时,它将调用下一个yield,依此类推。
关于enum_for有一个有趣的花絮。在线文档声明以下内容:
enum_for(method = :each, *args) → enum
Creates a new Enumerator which will enumerate by calling method on obj, passing args if any.
str = "xyz"
enum = str.enum_for(:each_byte)
enum.each { |b| puts b }
# => 120
# => 121
# => 122
如果未将符号指定为enum_for的参数,ruby会将枚举器挂接到接收方的each方法上。某些类没有String方法,例如String类。
str = "I like fruit"
enum = str.to_enum
enum.next
=> NoMethodError: undefined method `each' for "I like fruit":String
因此,对于某些用enum_for调用的对象,您必须明确说明枚举方法是什么。
答案 9 :(得分:0)
Yield 可以用作无名块来返回方法中的值。请考虑以下代码:
Def Up(anarg)
yield(anarg)
end
您可以创建一种方法&#34; Up&#34;分配一个参数。您现在可以将此参数分配给yield,该yield将调用并执行关联的块。您可以在参数列表之后分配块。
Up("Here is a string"){|x| x.reverse!; puts(x)}
当Up方法调用yield时,使用参数将其传递给block变量以处理请求。