Search code examples
rubyblockproc

Ruby - Why use a Block as a parameter?


I'm having trouble understanding Blocks vs Procs in ruby. I get the basic idea that a proc is a method saved as an object that you can call repeatedly without having to continue writing the same lines of code over and over.

My trouble lies with accepting blocks as parameters in methods.

The homework problem is super simple.

Write a method that accepts a block as a parameter and reverses all the words in a string.

Below is the answer they're looking for.

def reverser(&prc)
  sentence = prc.call
  words = sentence.split(" ")
  words.map { |word| word.reverse }.join(" ")
end

I have two questions -

1 How do you call this method because if I put

print reverser("Hello") 

I get an error "wrong number of arguments (given 1, expected 0)"

Second, why not just write the following method? What's the benefit of writing a method that takes a block?

def reverser(string)
  string.split.map{|x| x.reverse}.join(" ")
end 

Solution

    1. You'd call it like this:

      print reverser { "Hello" }
      

    Or if your block is several lines long then like this:

    print reverser do
      "Hello"
    end
    
    1. Blocks are close to what anonymous functions are in other languages.

    Map is a good example. What map does is it takes an array and maps (transforms) every element according to whatever rules you need.

    These mapping rules must be a code. That means, it can't be a string or number or something, it has to be a function. Block is used here as function which you can write using minimal code.

    So in your example you have this:

    words.map { |word| word.reverse }.join(" ")
    

    If you couldn't pass block to map then you'd have to define that function and pass it somewhere - and name that function. That is just not efficient.

    Lets change this block to work so that it reverses word only if it starts with capital letter.

    words.map do |word| 
      if word[0] =~ /[A-Z]/ 
        word.reverse
      else
        word
    end.join(" ")
    

    Without blocks you'd need to define that function, which you do not need in any other places and call that. That's just not efficient. This is what it would look like

    def reverse_if_starts_with_capital_letter(word)
      if word[0] =~ /[A-Z]/ 
        word.reverse
      else
        word
      end
    end
    
    # not sure if this syntax would work, just demonstrates idea
    words.map(&reverse_if_starts_with_capital_letter)