Answers:
是的,起初有点令人困惑。
在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
(注意,您可以随心所欲地调用此变量,但是调用它很有意义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?
如果不是可选的,则只需调用它即可。
编辑
@hmak创建了这些示例的repl.it:https://repl.it/@makstaks/blocksandyieldsrubyexample
racsO
如果 the_name = ""
"Oscar"
(答案中不是很清楚)
person.do_with_name {|string| yield string, something_else }
在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 =)
很有可能有人会在这里提供一个真正详细的答案,但是我一直发现Robert Sosinski的这篇文章很好地解释了块,proc和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,所以上面可能有错误。
我想补充一下为什么您会这样处理已经很不错的答案。
不知道您来自哪种语言,但是假设它是静态语言,这种情况看起来会很熟悉。这是您在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();
}
}
}
忽略整个流链接的事情,这个想法是
这就是你在红宝石中做的方式
File.open("readfile.rb", "r") do |infile|
while (line = infile.gets)
puts "#{counter}: #{line}"
counter = counter + 1
end
end
截然不同。分解这一个
在这里,您基本上不必将步骤一和步骤二委托给另一个类。如您所见,这大大减少了您必须编写的代码量,这使事情更易于阅读,并减少了内存泄漏或文件锁未清除的可能性。
现在,它不像您无法在Java中做类似的事情,事实上,人们已经这样做了数十年。这称为策略模式。区别在于,没有块时,对于诸如文件示例之类的简单事件,由于需要编写的类和方法数量过多,策略变得过分杀伤。使用块,这是一种简单而优雅的方法,以这种方式构造代码没有任何意义。
这不是使用块的唯一方法,但是其他方法(例如Builder模式,您可以在rails的form_for api中看到)足够相似,一旦将其包裹起来,应该很明显会发生什么。当您看到块时,通常可以放心地假设方法调用是您要执行的操作,而该块正在描述您要执行的操作。
File.readlines("readfile.rb").each_with_index do |line, index| puts "#{index + 1}: #{line}" end
更嘲笑Java专家。
IO.foreach('readfile.rb').each_with_index { |line, index| puts "#{index}: #{line}" }
再加上没有记忆问题)
我发现这篇文章非常有用。特别是以下示例:
#!/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
像您从源代码中看到的那样使用,但功能本质上是相同的,请参阅此问题进行讨论。)这为该功能执行一些初始化然后从调用代码中获取输入和然后根据需要进行处理。
换一种说法,它类似于将匿名函数作为参数然后在javascript中调用它的函数。
在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
我有时会这样使用“ yield”:
def add_to_http
"http://#{yield}"
end
puts add_to_http { "www.example.com" }
puts add_to_http { "www.victim.com"}
Logger
如果用户不需要,则不必执行某些任务。不过,您应该向自己解释...
关于产量,我想提出两点。首先,尽管这里有很多答案都讨论了将块传递给使用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方法上。某些类不具有each方法,例如String类。
str = "I like fruit"
enum = str.to_enum
enum.next
=> NoMethodError: undefined method `each' for "I like fruit":String
因此,在使用enum_for调用某些对象的情况下,必须明确说明枚举方法是什么。