Chef Essentials: Understanding Ruby Blocks and Yield

Using yield in Ruby is conceptually similar to passing a function as a parameter in other programming languages. It allows a method to execute code (a block) that is provided at the time of the method call, enabling dynamic and flexible behavior.


In Ruby, methods can receive a block of code without explicitly defining a parameter for it. The yield keyword is used within a method to transfer control to this block. The block can receive parameters from the method and can be executed multiple times from within the method.

Your Code Example:

def yield_greetings(name)
  puts "We're now in the method!"
  yield("Emily")
  puts "In between the yields!"
  yield(name)
  puts "We're back in method."
end

yield_greetings("Erick") { |n| puts "Hello #{n}." }

Explanation:

  • Method Definition:

    • yield_greetings is defined with one parameter, name.
    • Inside the method, yield is called twice, each time passing a parameter to the block.
  • Method Invocation:

    • The method is called with the argument "Erick" and a block { |n| puts "Hello #{n}." }.
    • The block takes one parameter n and outputs "Hello #{n}.".

Flow of Execution:

  1. The method prints "We're now in the method!".
  2. yield("Emily") transfers control to the block with "Emily" as the argument.
    • The block executes and prints "Hello Emily.".
  3. The method prints "In between the yields!".
  4. yield(name) transfers control to the block with "Erick" as the argument.
    • The block executes and prints "Hello Erick.".
  5. The method prints "We're back in method.".

Output:

We're now in the method!
Hello Emily.
In between the yields!
Hello Erick.
We're back in method.

In many languages, functions can be passed as arguments to other functions, which can then invoke them. This is similar to how blocks and yield work in Ruby.

function yieldGreetings(name, callback) {
  console.log("We're now in the method!");
  callback("Emily");
  console.log("In between the yields!");
  callback(name);
  console.log("We're back in method.");
}

yieldGreetings("Erick", function(n) {
  console.log(`Hello ${n}.`);
});

Output:

We're now in the method!
Hello Emily.
In between the yields!
Hello Erick.
We're back in method.

Explanation:

  • The yieldGreetings function takes a name and a callback function.
  • It calls callback("Emily") and callback(name), similar to how yield is used in Ruby.
def yield_greetings(name, callback):
    print("We're now in the method!")
    callback("Emily")
    print("In between the yields!")
    callback(name)
    print("We're back in method.")

yield_greetings("Erick", lambda n: print(f"Hello {n}."))

Output:

We're now in the method!
Hello Emily.
In between the yields!
Hello Erick.
We're back in method.

Explanation:

  • The yield_greetings function takes a name and a callback function.
  • It calls callback("Emily") and callback(name), similar to the Ruby method.

  • Passing Behavior (Code):

    • In all cases, you’re passing a piece of executable code to a function/method.
    • This allows the function/method to execute code defined outside its scope.
  • Method Control:

    • The method controls when and how many times the passed-in code is executed.
    • It can pass different arguments each time it invokes the code.
  • Flexibility and Reusability:

    • The method’s behavior can be customized without changing its internal implementation.
    • Different blocks/functions can be passed to alter its operation.

  • Implicit Block Passing:

    • In Ruby, a block is passed implicitly to a method without being a formal parameter.
    • Inside the method, yield is used to execute the block.
  • Parameters to the Block:

    • You can pass parameters to the block by including them in parentheses after yield.
    • The block can accept these parameters and use them in its execution.

You can also pass the block as an explicit parameter using &block, which converts the block into a Proc object.

Example:

def yield_greetings(name, &block)
  puts "We're now in the method!"
  block.call("Emily")
  puts "In between the yields!"
  block.call(name)
  puts "We're back in method."
end

yield_greetings("Erick") { |n| puts "Hello #{n}." }
  • Explanation:
    • &block captures the block passed to the method.
    • block.call is used to execute the block with the given arguments.
    • This approach is more similar to explicitly passing a function as a parameter.

  • First-Class Functions:

    • In programming languages that support first-class functions (like JavaScript and Python), functions can be treated like any other variable.
    • They can be passed as arguments, returned from other functions, and assigned to variables.
  • Higher-Order Functions:

    • Functions that take other functions as arguments or return them as results.
    • yield_greetings is acting as a higher-order function by executing the passed-in block.

  • Yes, Using yield is Similar to Passing Functions:

    • The yield keyword in Ruby allows a method to execute a block of code passed to it, similar to how functions can be passed and invoked in other languages.
  • Enhances Flexibility:

    • This mechanism allows methods to be more flexible and customizable.
    • The same method can perform different operations based on the block provided.
  • Dynamic Execution:

    • Methods can delegate certain operations to the block, allowing for dynamic behavior.

You can also use Proc or Lambda objects to pass functions explicitly.

Example with Proc:

def yield_greetings(name, greeting_proc)
  puts "We're now in the method!"
  greeting_proc.call("Emily")
  puts "In between the calls!"
  greeting_proc.call(name)
  puts "We're back in method."
end

greeting = Proc.new { |n| puts "Hello #{n}." }

yield_greetings("Erick", greeting)

Explanation:

  • A Proc object greeting is created with the desired code.
  • The greeting proc is passed to yield_greetings and called within the method.

  • Blocks in Ruby:

    • Provide a way to pass chunks of code to methods.
    • Used extensively in Ruby for iterators and callbacks.
  • yield Keyword:

    • Transfers control from the method to the block.
    • Can pass arguments to the block.
    • Simplifies the syntax for calling blocks.
  • Similarities with Other Languages:

    • The concept of passing executable code to functions/methods is common in many languages.
    • It allows for higher-order programming and greater abstraction.

  • Iterators:

    Ruby’s built-in methods like each use blocks:

    [1, 2, 3].each { |n| puts n * 2 }
    
  • Custom Methods with Dynamic Behavior:

    You can create methods that perform actions provided at the time of the call.

    def perform_twice
      yield
      yield
    end
    
    perform_twice { puts "Hello!" }
    

    Output:

    Hello!
    Hello!
    

  • Using yield in Ruby allows methods to execute blocks of code passed to them, similar to passing functions as parameters in other languages.
  • This feature enhances the flexibility and reusability of methods by decoupling their behavior from their implementation.
  • Understanding how yield and blocks work in Ruby can help you write more powerful and expressive code.