2

I'm an experienced developer but a Ruby newbie. I was reading this thread do..end vs curly braces for blocks in Ruby and learned some good stuff about when to use braces and when to use do .. end blocks. One of the tips was use do .. end when you are after side effects and {} when you are concerned about the return value. So experimenting with some of the examples in the enumerator tutorial I'm going through:

irb(main):001:0> my_array = [1, 2]
=> [1, 2]
irb(main):002:0> my_array.each {|num| num *= 2; puts "The new number is #{num}."}
The new number is 2.
The new number is 4.
=> [1, 2]
irb(main):003:0> my_array.each do |num| num *= 2; puts "The new number is #{num}." end
The new number is 2.
The new number is 4.
=> [1, 2]

Hang on. I thought the do..end block returns an enumerator object? It looks like an array. Let's check:

irb(main):004:0> puts my_array.each {|num| num *= 2; puts "The new number is #{num}."}
The new number is 2.
The new number is 4.
1
2
=> nil
irb(main):005:0> puts my_array.each do |num| num *= 2; puts "The new number is #{num}." end
#<Enumerator:0x000055967e53ac40>
=> nil

Okay, it is an enumerator. But what happened to the output of the puts call in the loop on line 005? The {} had the expected side effect but not the do..end block, which seems to violate that rule of thumb.

What happened to my "The new number is #{num}." strings?

coby101
  • 25
  • 4
  • Does [this](https://stackoverflow.com/a/5587399/2988) answer your question? – Jörg W Mittag Jul 01 '20 at 05:12
  • It explains the first part, which I probably should have left out of my question as I think I get that. But I'm more concerned with where the output of `puts "The new number is #{num}."` goes. – coby101 Jul 01 '20 at 09:55

1 Answers1

1

do...end and {} are 100% semantically equivalent for method blocks. Their only difference is their parsing precedence, so they evaluate differently

To really understand that difference, a few things first.

Ruby lets you call methods without parens:

my_object.my_method my_arg

# so my_arg could actually be a method! Let's put parens in to show that:
my_object.my_method(my_arg())

Blocks in Ruby are method arguments -- a syntax for passing in closures (except for the special keywords that act in the parent scope). The below two chunks are equivalent:

[1, 2, 3].map { |x| 2 * x }

# split out into two lines
double = ->(x) { 2 * x }  # shorthand for `lambda { |x| 2 * x }`
[1, 2, 3].map(&double)

Okay, so knowing all that, let's expose the difference between {} and do...end:

my_method [1, 2, 3].map { |x| 2 * x }

my_method([1, 2, 3].map { |x| 2 * x }) # parses like this


my_method [1, 2, 3].map do |x| 2 * x end

my_method([1, 2, 3].map) do |x| 2 * x end # parses like this

my_method([1, 2, 3].map) { |x| 2 * x }    # in other words

{} has more precedence than do...end, immediately getting associated to the method immediately to its left. do...end has lower precedence, and will associate with my_method, which gets passed [1, 2, 3].map and the block as arguments.

This means, what you did above is:

puts(my_array.each) { |num| num *= 2; puts "The new number is #{num}." }

You've passed into puts the my_array.each, which is an enumerator, and a block, and puts does nothing with the blocks passed into it, as do all methods by default.

Kache
  • 15,647
  • 12
  • 51
  • 79
  • Thanks for that, so well laid out and clear. I come from the world of Common Lisp so I can tell you I'm really pleased to learn Ruby has lambdas. I look forward to understanding this last point about methods ignoring by default block arguments as I work through all the tutorials that are on my plate ATM. Cheers. – coby101 Jul 01 '20 at 10:15
  • 1
    @coby101: There's no difference to Common Lisp there. If you pass a lambda to a function but that function never calls the lambda, then nothing happens. `puts` has no use for a block, so it will never call it. – Jörg W Mittag Jul 01 '20 at 10:39
  • @coby101 you may enjoy this article: http://www.randomhacks.net/2005/12/03/why-ruby-is-an-acceptable-lisp/ – Kache Jul 04 '20 at 05:09
  • Thanks, @Kache. It was an illuminating read and I almost got sucked into reading all the comments before I saw how long and dense they were! Many of the same issues and points of view I recall well from my days in comp.lang.lisp. Written 15 years ago, I get the impression that Ruby has grown and aged well. – coby101 Jul 07 '20 at 10:12