Ruby 块的最佳解释?

发布于 2024-10-16 14:48:44 字数 1432 浏览 7 评论 0原文

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

活泼老夫 2024-10-23 14:48:44

我提供了我自己的解释 这个答案,稍作修改:

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 方法正在传递一个块。)

因为块是 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?

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

这不是一个详尽的答案,因为它没有涵盖捕获块作为参数、它们如何处理数量、块参数中的不扩散等,但旨在作为 Blocks-Are-Lambdas 介绍。 >

I offer up my own explanation from this answer, slightly modified:

"Blocks" in Ruby are not the same as the general programming terms "code block" or "block of code".

Pretend for a moment that the following (invalid) Ruby code actually worked:

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"

While that code is invalid, its intent—passing some code to a method and having that method run the code—is possible in Ruby in a variety of ways. One of those ways is "Blocks".

A Block in Ruby is very, very much like a method: it can take some arguments and run code for those. Whenever you see foo{ |x,y,z| ... } or foo do |x,y,z| ... end, those are blocks that take three parameters and run the ... on them. (You might even see that the upto method above is being passed a block.)

Because Blocks are a special part of the Ruby syntax, every method is allowed to be passed a block. Whether or not the method uses the block is up to the method. For example:

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

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

The method above is passed a block that is ready to issue an insult, but since the method never calls the block, only the nice message is printed. Here's how we call the block from a method:

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.

We use block_given? to see whether or not a block was passed along or not. In this case we passed an argument back to the block; it's up to your method to decide what to pass to the block. For example:

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?

It's just a convention (and a good one, and one you want to support) for some classes to pass the instance just created to the block.

This is not an exhaustive answer, as it does not cover capturing blocks as arguments, how they handle arity, un-splatting in block parameters, etc. but intends to serve as a Blocks-Are-Lambdas intro.

绮筵 2024-10-23 14:48:44

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

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

    add1 = Proc.new {|x| x+1} # 返回其参数加一。
    add1.call(1) # =>; 2
    
  2. 作为方法伪参数传递,显式使用特殊的 & 最后参数语法糖运算符或隐式使用 block_given?/产量对:

    def times_do(&proc) # “proc”是调用此方法的块。
      2.times { proc.call() } 如果 proc
    结尾
    times_do { puts "OK" } # 在不同的行上打印两次“OK”。
    
    def thrice_do() # 如果给出了一个块,则可以使用“yield”调用它。
      3.times { 产量 } if block_given?
    结尾
    thrice_do { put "OK" } # 在单独的行上打印三次“OK”。
    

第二种形式通常用于访问者模式;数据可以作为 callyield 方法的参数传递给特殊块参数。

Ruby blocks are a way of creating Proc objects which represent code that can be used by other code. Proc objects are instructions between curly braces {} (or do...end phrases for multiline blocks, which have lower precedence than curly braces) which may optionally take arguments and return values (e.g. {|x,y| x+y}). Procs are first-class objects and can be constructed explicitly or attained implicitly as method pseudo-arguments:

  1. Construction as a Proc object (or using the lambda keyword):

    add1 = Proc.new {|x| x+1} # Returns its argument plus one.
    add1.call(1) # => 2
    
  2. Passed as a method pseudo argument, either explicitly using the special & last-argument syntax sugar operator or implicitly using a block_given?/yield pair:

    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.
    

The second form is typically used for Visitor patterns; data can be passed to the special block arguments as arguments to the call or yield methods.

若沐 2024-10-23 14:48:44

来自Why(令人心酸的)Ruby 指南

任何用大括号括起来的代码都是
一个区块。

2.times { print "是的,我用过
在我的例子中是厚块培根,但从来没有
再次!” }
就是一个例子。

使用块,您可以将一组
一起指示,以便他们能够
被传递到你的程序中。这
大括号看起来像
螃蟹钳子已经抢走了
代码并将其结合在一起。什么时候
你看到这两个钳子,记住
里面的代码已经被按下了
成一个单元。这就像其中之一
那些小凯蒂猫盒子
在塞满了东西的商场里卖
小铅笔和微型纸,
全部挤进闪闪发光的
可隐藏的透明外壳
在您的手掌中进行隐蔽固定
运营。除了块没有
需要那么多的眯眼。卷发的
牙套也可以交易
词 do 和 end,如果
你的块比一行长。

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 出现
鹰向下展开,而它们整齐地
交叉双腿。)这个滑道的作用是
街区和街区之间的通道
他们周围的世界。变量是
通过这个滑道(或隧道)
进入区块。

From Why's (poignant) guide to ruby:

Any code surrounded by curly braces is
a block.

2.times { print "Yes, I've used
chunky bacon in my examples, but never
again!" }
is an example.

With blocks, you can group a set of
instructions together so that they can
be passed around your program. The
curly braces give the appearance of
crab pincers that have snatched the
code and are holding it together. When
you see these two pincers, remember
that the code inside has been pressed
into a single unit. It’s like one of
those little Hello Kitty boxes they
sell at the mall that’s stuffed with
tiny pencils and microscopic paper,
all crammed into a glittery
transparent case that can be concealed
in your palm for covert stationary
operations. Except that blocks don’t
require so much squinting. The curly
braces can also be traded for the
words do and end, which is nice if
your block is longer than one line.

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

Block arguments are a
set of variables surrounded by pipe
characters and separated by commas.

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

Block arguments are used
at the beginning of a block.

{ |x,y| x + y }

In the above example, |x,y| are the arguments. After the arguments, we
have a bit of code. The expression x +
y adds the two arguments together. I
like to think of the pipe characters
as representing a tunnel. They give
the appearance of a chute that the
variables are sliding down. (An x goes
down spread eagle, while the y neatly
crosses her legs.) This chute acts as
a passageway between blocks and the
world around them. Variables are
passed through this chute (or tunnel)
into the block.

蹲墙角沉默 2024-10-23 14:48:44

对于具有 C# 背景(或其他语言)背景的任何人来说,这可能会有所帮助:

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

红宝石: <代码>{ |x| x + 1 }

C#: x => x + 1

Ruby: { |name|放置“你好#{name}”}

C#:name => { Console.WriteLine("你好 {0}", name); 。

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

Ruby:

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

C#:

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

在 Ruby 和 C# 中,都允许使用多个语句。在 Ruby 中,需要使用上面的第二种语法。

这些概念在许多其他受到函数式编程背后的思想影响的语言中也可用。

For anybody coming to this question from a C# background (or other langs really), this might help:

Ruby blocks are like lambda expressions and anonymous methods in C#. They are what C# calls delegates (and Ruby calls Procs), which is to say that they are essentially functions that can be passed as values. In both Ruby and C#, they can also behave as closures.

Ruby: { |x| x + 1 }

C#: x => x + 1

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

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

Both C# and Ruby offer alternative ways to write the above example.

Ruby:

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

C#:

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

In both Ruby and C#, multiple statements are allowed, In Ruby, the second syntax above is required for this.

These concepts are available in many other languages that have been influenced by the ideas behind functional programming.

眼泪淡了忧伤 2024-10-23 14:48:44

Programming Ruby》一书有很棒的块的说明以及使用它们

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

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

;c,d 在块内声明两个新的局部变量,它们不接收来自被调用的值-routine 的 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

The book "Programming Ruby" has a great explanation of blocks and using them.

In 1.9+, the parameter list passed into a block became more sophisticated, allowing local variables to be defined:

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

;c,d declare two new local variables inside the block, that do not receive values from the called-routine's yield statement. Ruby 1.9+ guarantees that, if the variables existed outside the block, that they will not be stomped on by the same-named variables inside the block. This is new behavior; 1.8 would stomp on them.

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

There's also the "splat" operator *, which works in the list of parameters:

do |a,*b| 
  some_stuff
end

Would assign the first of multiple values to "a", and all the rest would be captured in "b" which would be treated like an array. The * could be on the a variable:

do |*a,b| 
  some_stuff
end

would capture all passed in variables except the last one, which would be passed to b. And, similarly to the two previous:

do |a,*b,c| 
  some_stuff
end

would assign the first value to a, the last value to c and all/any intervening values to b.

I think that's pretty powerful and slick.

For instance:

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
彩虹直至黑白 2024-10-23 14:48:44

块是匿名一等过程的轻量级文字,具有一些恼人的限制。它们在 Ruby 中的工作方式与在几乎所有其他编程语言中的工作方式相同,模数上述限制,即:

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

Blocks are lightweight literals for anonymous first-class procedures with some annoying limitations. They work the same way in Ruby as they work in pretty much every other programming language, modulo the afore-mentioned limitations, which are:

  • blocks can only appear in argument lists
  • at most one block can appear in an argument list (and it must be the last argument)
梦行七里 2024-10-23 14:48:44

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

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

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

块可以接收竖线 || 内以逗号分隔的参数列表。例如:

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

块(在 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>

Blocks are a way of grouping code in Ruby. There are two ways to write blocks. One is using the do..end statement and the other is surrounding the code in curly braces: {}. Blocks are considered objects in the Ruby programming language, and by default all functions accept an implicit block argument.

Here are two examples of blocks that do the same thing:

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

Blocks can receive lists of comma-separated arguments inside vertical bars ||. For example:

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

Blocks (in ruby 1.9.2) can explicitly have local variables:

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

=> world
=> world

Local variables can be combined with parameters:

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

All functions can receive a default block argument:

def twice
  yield
  yield
end

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

What is the difference between do..end and {} blocks? By convention {} blocks are on a single line and do..end blocks span multiple lines, since they are each easier to read this way. The main difference has to do with precedence though:

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>
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文