Ruby中的块和产量
我想了解块和yield
,以及他们如何在Ruby中工作。
如何使用yield
? 我看过的许多Rails应用程序都使用了一种奇怪的方式。
有人可以向我解释或告诉我去哪里了解他们吗?
是的,起初有点令人费解。
在Ruby中,方法可能会收到一个代码块,以执行任意代码段。
当一个方法需要一个块时,它通过调用yield
函数来调用它。
例如,这是非常方便的迭代列表或提供自定义algorithm。
以下面的例子:
我要定义一个用名称初始化的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
注意,块接收一个名为variables的参数(注意,你可以调用这个variables,但是这个variables是有意义的)。 当代码调用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"]
或者,我们也可以提供一个自定义的sortingalgorithm,例如基于string的大小:
days.sort do |x,y| x.size <=> y.size end => ["monday", "friday", "tuesday", "thursday", "wednesday"]
我希望这可以帮助你更好地理解它。
顺便说一句,如果该块是可选的,你应该这样称呼它:
yield(value) if block_given?
如果不是可选的,只需调用它。
很有可能有人会在这里提供一个真正详细的答案,但是我总是发现罗伯特·索辛斯基(Robert Sosinski)的这篇文章是对块,过程和lambda之间细微差别的一个很好的解释。
我应该补充说,我相信我链接到的post是特定于ruby1.8。 有些东西在Ruby 1.9中已经改变了,比如块variables是本地块。 在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,所以上面可能有一个错误。
在Ruby中,方法可以检查是否调用了除正常参数之外的块。 通常这是使用block_given?
完成的block_given?
方法,但您也可以通过在最终参数名称前面加一个&符号( &
)来将该块称为显式Proc。
如果一个方法被一个块调用,那么如果需要的话,该方法可以用一些参数来控制块(调用块)。 考虑这个示例方法,它演示了:
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 =)
我想补充一下,为什么你会这样做,已经很好的答案。
不知道你来自哪种语言,但假设它是一种静态的语言,这种事情看起来很熟悉。 这是你如何阅读在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(); } } }
忽略整个stream连锁的事情,这个想法是这样的
- 初始化需要清理的资源
- 使用资源
- 一定要把它清理干净
这是你怎么做的ruby
File.open("readfile.rb", "r") do |infile| while (line = infile.gets) puts "#{counter}: #{line}" counter = counter + 1 end end
狂野不同。 打破这一个
- 告诉File类如何初始化资源
- 告诉文件类如何处理它
- 嘲笑还在打字的java家伙;-)
在这里,不是处理第一步和第二步,而是基本上将其委托给另一个class级。 正如你所看到的那样,这大大减less了你必须编写的代码量,这使得事情更容易阅读,并且减less了内存泄漏或文件locking没有被清除的可能性。
现在,它不像你在java中做类似的事情,事实上,人们已经做了几十年了。 这就是所谓的战略模式。 不同的是,没有块,对于像文件例子那样简单的东西,由于需要编写的类和方法的数量,策略变得过度。 对于块来说,这是一种简单而优雅的方式,因此没有必要以这种方式构造代码。
这不是使用块的唯一方式,但其他(如Builder模式,可以在rails中的form_for api中看到)足够相似,以至于一旦您将头部缠绕在此之上,就会发生什么。 当你看到块时,通常认为方法调用是你想要做的,而块描述了你想要做什么。
我发现这篇文章是非常有用的。 特别是下面的例子:
#!/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
所以基本上每次调用ruby都会在do
块或内部运行代码。 如果提供一个参数来yield
那么这将作为参数提供给do
块。
对我来说,这是我第一次真正了解这些街区做什么。 它基本上是一种让函数访问内部数据结构的方法,用于迭代或configuration函数。
所以当你在轨道上写下如下:
respond_to do |format| format.html { render template: "my/view", layout: 'my_layout' } end
这将运行respond_to
函数,该函数产生具有(内部) format
参数的do
块。 然后调用这个内部variables的.html
函数,然后产生代码块来运行render
命令。 请注意, .html
只会在要求的文件格式下产生。 (技术性:这些函数实际上使用block.call
而不是从源代码中看到,但function本质上是一样的,请参阅这个问题进行讨论。)这为函数提供了一种方法来执行一些初始化,然后从调用代码,然后进行处理,如果需要的话。
或者换一种说法,它类似于以匿名函数作为参数的函数,然后在javascript中调用它。
我有时候会这样使用“yield”
def add_to_http "http://#{yield}" end puts add_to_http { "www.example.com" } puts add_to_http { "www.victim.com"}
产量,简单来说,允许你创build的方法来调用块。 yield关键字特别是块中的“stuff”将被执行的位置。
在Ruby中,块基本上是一段代码,可以通过任何方法传递和执行。 块总是与方法一起使用,通常将数据提供给它们(作为参数)。
块被广泛用于Ruby gem(包括Rails)和精心编写的Ruby代码中。 它们不是对象,因此不能被分配给variables。
基本语法
块是由{}或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语句时,期望在调用的时候接收一个block。 如果没有提供块,则在yield语句到达时抛出exception。 我们可以使该块可选,避免引发exception:
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 Up(anarg) yield(anarg) end
你可以创build一个方法“向上”,它被分配了一个参数。 你现在可以分配这个参数来产生哪个将会调用和执行一个关联的块。 您可以在参数列表之后分配块。
Up("Here is a string"){|x| x.reverse!; puts(x)}
当Up方法使用参数调用yield时,会传递给blockvariables来处理请求。