Ruby块的最佳解释?

时间:2011-02-06 03:27:53

标签: ruby block

您可以分享的Ruby块的最佳解释是什么?

使用和编写可以占用块的代码吗?

7 个答案:

答案 0 :(得分:36)

我从this answer提出了我自己的解释,略有修改:

Ruby中的“块”与通用编程术语“代码块”或“代码块”不同。

暂时假装以下(无效的)Ruby代码实际工作:

def add10( n )
  puts "#{n} + 10 = #{n+10}"
end

def do_something_with_digits( method )
  1.upto(9) do |i|
    method(i)
  end
end

do_something_with_digits( add10 )
#=> "1 + 10 = 11"
#=> "2 + 10 = 12"
...
#=> "9 + 10 = 19"

虽然该代码无效,但它的意图 - 将一些代码传递给方法并使该方法运行代码 - 在Ruby中可以通过各种方式实现。其中一种方法是“阻止”。

Ruby中的一个块非常非常像一个方法:它可以采用一些参数并运行代码。每当您看到foo{ |x,y,z| ... }foo do |x,y,z| ... end时,这些是带有三个参数并在其上运行...的块。 (您甚至可能会看到上面的upto方法正在传递一个块。)

因为Blocks是Ruby语法的特殊部分,所以允许每个方法传递一个块。 方法是否使用块取决于方法。例如:

def say_hi( name )
  puts "Hi, #{name}!"
end

say_hi("Mom") do
  puts "YOU SUCK!"
end
#=> Hi, Mom!

上面的方法传递了一个准备发出侮辱的块,但由于该方法从不调用块,因此只打印好消息。以下是我们如何从方法中调用块:

def say_hi( name )
  puts "Hi, #{name}!"
  if block_given?
    yield( name )
  end
end

say_hi("Mridang") do |str|
  puts "Your name has #{str.length} letters."
end
#=> Hi, Mridang!
#=> Your name has 7 letters.

我们使用block_given?来查看是否传递了一个块。在这种情况下,我们将一个参数传递回块;这取决于你的方法来决定传递给块的内容。例如:

def say_hi( name )
  puts "Hi, #{name}!"
  yield( name, name.reverse ) if block_given?
end

say_hi("Mridang"){ |str1, str2| puts "Is your name #{str1} or #{str2}?" }
#=> Hi, Mridang!
#=> Is your name Mridang or gnadirM?

这只是一个约定(一个好的,一个你想支持的),一些类将刚创建的实例传递给块。

这不是一个详尽的答案,因为它不包括捕获块作为参数,它们如何处理arity,在块参数中解除映射等等,但是打算充当Blocks-Are-Lambdas简介。

答案 1 :(得分:27)

Ruby块是一种创建Proc objects的方法,它代表了其他代码可以使用的代码。 Proc对象是花括号{}(或多行块的do...end短语)之间的指令,它们的优先级低于花括号,可以选择接受参数并返回值(例如{|x,y| x+y})。 Proc是first-class objects,可以显式构造或隐式获得方法伪参数:

  1. 构建为Proc个对象(或使用lambda关键字):

    add1 = Proc.new {|x| x+1} # Returns its argument plus one.
    add1.call(1) # => 2
    
  2. 作为方法伪参数传递,显式使用特殊的& last-argument语法Sugar运算符或隐式使用block_given? / yield对:

    def twice_do(&proc) # "proc" is the block given to a call of this method.
      2.times { proc.call() } if proc
    end
    twice_do { puts "OK" } # Prints "OK" twice on separate lines.
    
    def thrice_do() # if a block is given it can be called with "yield".
      3.times { yield } if block_given?
    end
    thrice_do { puts "OK" } # Prints "OK" thrice on separate lines.
    
  3. 第二种形式通常用于Visitor patterns;数据可以作为callyield方法的参数传递给特殊块参数。

答案 2 :(得分:20)

来自Why's (poignant) guide to ruby

  

花括号包围的任何代码都是   一个街区。

     

2.times { print "Yes, I've used chunky bacon in my examples, but never again!" }就是一个例子。

     

使用块,您可以对一组进行分组   一起说明,以便他们可以   传递你的程序。该   花括号给出了外观   螃蟹钳子抢了   代码并将它们放在一起。什么时候   你还看到这两个钳子,记住   内部代码已被按下   成一个单位。这就像是其中之一   那些小Hello Kitty盒子   在满是的商场里卖   小铅笔和微观纸,   所有人都陷入了闪光   可隐藏的透明表壳   在你的手掌中隐蔽的静止   操作。除了块没有   需要这么多眯眼。卷曲的   大括号也可以交易   单词做和结束,这很好,如果   你的块长于一行。

loop do
  print "Much better."    
  print "Ah. More space!"
  print "My back was killin' me in those crab pincers."
end
  

块参数是一个   管道包围的变量集   字符以逗号分隔。

|x|, |x,y|, and |up, down, all_around| are examples.
  

使用块参数   在一个街区的开头。

{ |x,y| x + y }
  

在上面的例子中,| x,y |是争论。争论之后,我们   有一点代码。表达式x +   y将两个参数加在一起。一世   想想管道人物   代表一条隧道。他们给   一个滑槽的外观   变量正在下滑。 (安x去了   向下蔓延的鹰,而y整齐   穿过她的腿。)这个滑道充当   街区和街区之间的通道   他们周围的世界。变量是   通过这个滑槽(或隧道)   进入街区。

答案 3 :(得分:7)

对于任何从C#背景(或其他langs)来到这个问题的人来说,这可能会有所帮助:

Ruby块就像lambda表达式和C#中的匿名方法。它们是C#调用委托(和Ruby调用Procs),也就是说它们本质上是可以作为值传递的函数。在Ruby和C#中,它们也可以表现为闭包。

Ruby:{ |x| x + 1 }

C#:x => x + 1

Ruby:{ |name| puts "Hello there #{name}" }

C#:name => { Console.WriteLine("Hello there {0}", name); }

C#和Ruby都提供了编写上述示例的替代方法。

红宝石:

do |name|
   puts "Hello there #{name}"
end

C#:

delegate(string name)
{
   Console.WriteLine("Hello there {0}", name);
}

在Ruby和C#中,允许多个语句,在Ruby中,上面的第二个语法是必需的。

许多其他语言都提供了这些概念,这些语言受到函数式编程背后的思想的影响。

答案 4 :(得分:6)

Programming Ruby”这本书很棒explanation of blocks and using them

在1.9+中,传递到块中的参数列表变得更加复杂,允许定义局部变量:

do |a,b;c,d| 
  some_stuff
end

;c,d在块内声明两个新的局部变量,它们不会从被调用例程的yield语句中接收值。 Ruby 1.9+保证,如果变量存在于块之外,则它们不会被块内的同名变量所踩踏。这是新的行为; 1.8会踩到它们。

def blah
  yield 1,2,3,4
end

c = 'foo'
d = 'bar'

blah { |a, *b; c,d|
  c = 'hello'
  d = 'world'
  puts "a: #{a}", "b: #{b.join(',')}", "c: #{c}", "d: #{d}" 
}

puts c, d
# >> a: 1
# >> b: 2,3,4
# >> c: hello
# >> d: world
# >> foo
# >> bar

还有“splat”运算符*,它在参数列表中起作用:

do |a,*b| 
  some_stuff
end

将多个值中的第一个赋值为“a”,其余所有值都将在“b”中捕获,这将被视为数组。 *可能位于a变量:

do |*a,b| 
  some_stuff
end

将捕获除最后一个变量之外的所有传入变量,这些变量将传递给b。并且,与前两个类似:

do |a,*b,c| 
  some_stuff
end

会将第一个值分配给a,将最后一个值分配给c,将所有/任何介入值分配给b

我认为这非常强大而且光滑。

例如:

def blah
  yield 1,2,3,4
end

blah { |a, *b| puts "a: #{a}", "b: #{b.join(',')}" }
# >> a: 1
# >> b: 2,3,4

blah { |*a, b| puts "a: #{a.join(',')}", "b: #{b}" }
# >> a: 1,2,3
# >> b: 4

blah { |a, *b, c| puts "a: #{a}", "b: #{b.join(',')}", "c: #{c}" }
# >> a: 1
# >> b: 2,3
# >> c: 4

答案 5 :(得分:3)

块是一种在Ruby中对代码进行分组的方法。有两种方法可以编写块。一个是使用do..end语句,另一个是围绕花括号中的代码:{}。块被认为是Ruby编程语言中的对象,默认情况下,所有函数都接受隐式块参数。

以下是两个执行相同操作的块的示例:

2.times { puts 'hi' }
2.times do
  puts 'hi'
end

Blocks可以在竖线条||中接收以逗号分隔的参数列表。例如:

[1,2].map{ |n| n+2 } # [3, 4]

Blocks(在ruby 1.9.2中)可以显式地拥有局部变量:

x = 'hello'
2.times do |;x|
  x = 'world'
  puts x
end

=> world
=> world

局部变量可以与参数结合使用:

[1,2].map{ |n;x| n+2 }

所有函数都可以接收默认的块参数:

def twice
  yield
  yield
end

twice { puts 'hello' }
=> hello
=> hello

do..end和{}块有什么区别?按惯例{}块在一行上,并且do..end块跨越多行,因为它们更容易以这种方式读取。主要的区别与优先权有关:

array = [1,2]

puts array.map{ |n| n*10 } # puts (array.map{ |n| n*10 })
=> 10
=> 20

puts array.map do |n| n*10 end # (puts array.map) do |n| n*10 end
=> <Enumerator:0x00000100862670>

答案 6 :(得分:3)

块是匿名一流程序的轻量级文字,有一些烦人的限制。它们在Ruby中的工作方式与在几乎所有其他编程语言中工作相同,模仿上述限制,其中包括:

  • 块只能出现在参数列表中
  • 最多一个块可以出现在参数列表中(并且它必须是最后一个参数)