Best explanation of Ruby blocks?

25,726

Solution 1

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.

Solution 2

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.

Solution 3

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.

Solution 4

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.

Solution 5

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
Share:
25,726
Blankman
Author by

Blankman

... .. . blank

Updated on December 08, 2020

Comments

  • Blankman
    Blankman over 3 years

    What is the best explanation for Ruby blocks that you can share?

    Both usage and writing code that can take a block?

  • maerics
    maerics about 13 years
    Good answer but the relationship to Proc objects seems essential, no?
  • Phrogz
    Phrogz about 13 years
    @maerics Essential to an exhaustive resource on Blocks? Yes. Essential to an explanation of blocks (which I interpret as being an introduction to them for the novice)? Definitely not, IMO.
  • Meltemi
    Meltemi almost 13 years
    "Any code surrounded by curly braces is a block" unless it is a hash.
  • Green
    Green about 11 years
    Braces have a high precedence; do has a low precedence. If the method invocation has parameters that are not enclosed in parentheses, the brace form of a block will bind to the last parameter, not to the overall invocation. The do form will bind to the invocation.
  • David 天宇 Wong
    David 天宇 Wong almost 10 years
    You don't explain what these examples return. I don't get it.
  • Victor Eijkhout
    Victor Eijkhout over 8 years
    Thanks. Yours is the only answer that helped me understand why {puts "hello"} does not work. Not allowed at all? That's weird.
  • BenKoshy
    BenKoshy over 8 years
    English, please! ......"Ruby blocks are syntax literals for Proc objects...." - if people don't know what a block is, I am guessing they won't know what "syntax literals for Proc objects" means either. try to explain as if readers were 5 years old.
  • Derrick Mar
    Derrick Mar over 8 years
    What is a syntax literal?
  • maerics
    maerics over 8 years
    @DerrickMar: by "syntax literal" i meant essentially "the arrangement of language tokens in their most basic sense". For example, in JavaScript, the sequence of characters /\d+/ is a syntax literal meaning "a regular expression matching one or more digits". Similarly, blocks are a direct way of defining procedures in Ruby which can be passed around as first-class objects (arguments-to and return-values-from other methods and procedures in Ruby).
  • Benjamints
    Benjamints almost 7 years
    Please be my tutor! Thank you for explaining it in such a straightforward and clear way.
  • Akash Pinnaka
    Akash Pinnaka about 6 years
    Surprised to see, there are only 29(including mine) upvotes for the answer in more than 7 years. The concept can be known. But the way you explain, "KUDOS!". Definitely recommended for beginners.