-1

I am not able to understand the logic of this code:

class VowelFinder
  include Enumerable
  def initialize(string)
    @string = string
  end

  def each
    @string.scan(/[aeiou]/) do |vowel|
      yield vowel
    end
  end
end

vf = VowelFinder.new("the quick brown fox jumped")
vf.inject(:+) # => "euiooue"
  1. How is the object vf.inject(:+) calling the method each in this program?

  2. How does the each method work in this program, because there is no block argument mentioned in function definition?

  3. If I simply call vf.each, why am I getting the following error?

    vowel_finder.rb:8:in `block in each': no block given (yield) (LocalJumpError)
    from vowel_finder.rb:8:in `scan'
    from vowel_finder.rb:8:in `each'
    from vowel_finder.rb:13:in `<main>'
    

One of the few things that I understood is that the each method in this class overrides the each method from the included Enumerable module. Other than that I did not understand anything about each and blocks.

Could someone please explain me the logic and how it works internally?

Michael Gaskill
  • 7,913
  • 10
  • 38
  • 43
  • 1
    Possible duplicate of [Blocks and yields in Ruby](http://stackoverflow.com/questions/3066703/blocks-and-yields-in-ruby) – Dave Schweisguth May 18 '16 at 15:09
  • May be going through the article [Building Enumerable & Enumerator in Ruby](https://www.practicingruby.com/articles/building-enumerable-and-enumerator) will make things clear – Wand Maker May 18 '16 at 15:35

1 Answers1

1

The inject method comes from the Enumerable class, which is included in VowelFinder. As part of the implementation of inject, it calls a method called each.

inject expects that the object that it's called on has an each method defined, as Enumerable does not provide an each method. Typically, inject is called on instances of container classes, such as Array and Hash, which do define each in terms of their container design.

Thus, when vf.inject(:+) calls each, it ends up calling VowelFinder#each, the object being operated on is vf and each is defined on VowelFinder.

In Ruby, you can declare a method that implicitly accepts a block, as each does. Technically, all methods in Ruby accept a block, but only some methods call a block. The yield statement is used to call the block.

Ruby has a built-in function called block_given? that's used to detect whether a block has been passed to the method. Many of the methods on Array and Hash use block_given? to determine whether to call the block or return an Enumerator. Checking block_given? allows the block to be called conditionally, like so:

def each
  if block_given?
    @string.scan(/[aeiou]/) do |vowel|
      yield vowel
    end
  else
    # Do something interesting here, or better yet, return an Enumerator
  end
end

In this case, each may be called this way:

vf = VowelFinder.new("the quick brown fox jumped")
vf.each {|vowel| puts "Found the vowel: #{vowel}" }

or this way:

vf.each

The else in the VowelFinder#each method is left for a learning exercise to return an enumerator, as is the convention.

The Enumerators and Enumerables chapter at RubyMonk has very good coverage of this topic.

Michael Gaskill
  • 7,913
  • 10
  • 38
  • 43