Dwi Wahyudi

Senior Software Engineer (Ruby, Golang, Java)


Overview

So recently I have a question from another dev regarding Ruby block, on how to read it. For some developers, it can be quite confusing at first, but Ruby block is just a closure (similar to closure in JavaScript). With that closure we “package” the function and all the variables inside as a section of code that can be passed as parameter to other functions. That section of code (which is a block or proc in Ruby) is THE closure.

In this Ruby code: [1, 2, 3].each { |num| puts num }.

{ |num| puts num } here is THE block.

We can store it as variable with a proc, a_proc = Proc.new({ |num| puts num })

By coincidence, recently, in Golang community, some developers are angry about iterator pattern, that will be introduced in Go 1.23 https://www.gingerbill.org/article/2024/06/17/go-iterator-design/

I recently saw a post on Twitter showing the upcoming Go iterator design for Go 1.23 (August 2024). From what I can gather, many people seem to dislike the design.

I can understand them, introducing a new concept can be tricky for some people, especially now we can do many ways in order to do one thing. Now Golang is a functional programming language as well.

Will you do such iteration with imperative way or functional way? Your choice.

Practical Block Example

Anyway, so I quickly searched for “Custom ruby iterator”, and found this article: https://clouddevs.com/ruby/create-custom-iterator/. Iterator pattern usually follows functional programming paradigm with block / first class function as its foundation.

The explanation and the example are short and clear, except for the class name, which I rename to SquareIterator.

class SquareIterator
  def initialize(numbers)
    @numbers = numbers
  end

  def iterate
    index = 0
    while index < @numbers.length
      yield @numbers[index] * @numbers[index]
      index += 1
    end
  end
end

numbers = [1, 2, 3, 4, 5]
iterator = SquareIterator.new(numbers)

iterator.iterate do |squared|
  puts "Squared value: #{squared}"
end

The output will be like this:

Squared value: 1
Squared value: 4
Squared value: 9
Squared value: 16
Squared value: 25

Well, this code section is the block/closure:

do |squared|
  puts "Squared value: #{squared}"
end

For people who are used to imperative code (Golang developers for example), this piece of code may be confusing and hard to understand. We’ll talk about in a moment. If you’re a Ruby developer and is still confused by how block works, skip until Closure topic below.

That yield call is quite confounding yet mysterious for people who first encounters it. In Ruby, block is just a piece of function body.

We can call iterate like this: iterator.iterate { |squared| puts "Squared value: #{squared}" }

But we cannot do it like this: iterator.iterate({ |squared| puts "Squared value: #{squared}" })

Because block is NOT a variable. It’s a block… and it’s not an object in Ruby.

We can visually imagine this block operation as follows:

  • In the block example, imagine this puts "Squared value: #{squared}" block body to replace the yield place there.
  • Then, imagine the squared block param there as the place for yield param.
  • yield param @numbers[index] * @numbers[index] will take place the squared block param.
  • Because the yield is inside a loop, repeat this operation multiple times as well. So we get repeated puts into the console.
  def iterate
    index = 0
    while index < @numbers.length
      # yield @numbers[index] * @numbers[index]
      puts "Squared value: #{@numbers[index] * @numbers[index]}"
      index += 1
    end
  end

  # ...

  iterator.iterate
  # iterator.iterate do |squared|
  #  puts "Squared value: #{squared}"
  # end

So, put the block body to replace the yield, use yield params to replace the block params.

If there are more than 1 params, it would be like this:

yield(a, b, c, d, e)

example_method do |a, b, c, d, e|

So in relationships with collection of data (array), we can place this block with this kind of thinking:

“For a collection of things (array), what do we want to do? Can we make those iterate function (to square the numbers) reusable?”

If yes, then block can be used. Let say from this iteration, we want to repeat a certain operation.

iterator.iterate do |squared|
  repeat_something(squared)
end

This is the goal of “Iteration design pattern”. To reuse the iteration.

Implicit or Explicit Block

What if there’s no block, and we just call it like iterator.iterate only. It will raise error, because yield will raise error if no block is given. We can make sure that this code won’t be broken if such condition happened.

We can check if implicit block is given by checking block_given? condition. Like this:

yield @numbers[index] * @numbers[index] if block_given?

We won’t yield anything to the caller if no block is given. It will just iterate with this condition while index < @numbers.length and doing nothing.

Previous examples used implicit block, there’s no block argument in iterate method. We can make it explicit by adding block parameter like this:

class SquareIterator
  def initialize(numbers)
    @numbers = numbers
  end

  def iterate(&block)
    index = 0
    while index < @numbers.length
      block.call(@numbers[index] * @numbers[index]) # Call to explicit block.
      index += 1
    end
  end
end

# The rest of code is still the same.

numbers = [1, 2, 3, 4, 5]
iterator = SquareIterator.new(numbers)

iterator.iterate do |squared|
  puts "Squared value: #{squared}"
end

Anyway, with this block being implicit or not, how does block and all of this functional programming even work?

Well the reason for us to discuss explicit block, is that in my opinion it is easier to explain about closure and functional programming as a whole by using explicit block.

Closure

Let’s add some changes, we want to store the squared values to an array.

class SquareIterator
  def initialize(numbers)
    @numbers = numbers
  end

  def iterate(&block)
    index = 0
    while index < @numbers.length
      block.call(@numbers[index] * @numbers[index]) # Call to explicit block.
      index += 1
    end
  end
end

numbers = [1, 2, 3, 4, 5]
iterator = SquareIterator.new(numbers)

# new array to store squared numbers, will be part of the block/closure.
squared_numbers = []

iterator.iterate do |squared|
  squared_numbers << squared
  puts "Squared value: #{squared}"
end

puts squared_numbers

Variable squared_numbers will be [1,4,9,16,25].

But wait, we understand that block body will be passed as block into the iterate method. How does iterate method know about squared_numbers? squared_numbers doesn’t belong to SquareIterator or iterate scope.

This is how closure works. Ruby compiler/interpreter will understand that squared_numbers belongs to the block/closure and refers to the squared_numbers declaration outside the SquareIterator class. Ruby compiler while in iterate method, knows the variable of squared_numbers declared from outside iterate or SquareIterator scope and able to update the variable as well.

Many other programming languages support Closure, for example JS and Golang.

https://en.wikipedia.org/wiki/Closure_(computer_programming)

Proc and Lambda Example

Let’s assign such block to a variable, with a proc, like this:

class SquareIterator
  def initialize(numbers)
    @numbers = numbers
  end

  def iterate(a_proc)
    index = 0
    while index < @numbers.length
      a_proc.call @numbers[index] * @numbers[index]
      index += 1
    end
  end
end

numbers = [1, 2, 3, 4, 5]
iterator = SquareIterator.new(numbers)

a_proc = Proc.new { |squared|  puts "Squared value: #{squared}" }

iterator.iterate(a_proc)

We just need to change yield to a_proc.call.

It’s almost identical. Call to block and proc is slower and explicit. I personally prefer it over the implicit yield.

And there’s another thing called lambda.

lambda = lambda { |squared|  puts "Squared value: #{squared}" }
lambda = -> (squared) { puts "Squared value: #{squared}" } # a shorthand syntax

iterator.iterate(lambda)

It’s almost the same thing with proc.

  • Lambda checks the arguments number (wrong arguments number will raise error), while proc doesn’t (will just assign missing arguments as nil) and

  • when a lambda returns, return value and execution flow goes back to the calling method while proc doesn’t (will just return immediately).

    def lambda_example
      a_lambda = lambda { return "Inside lambda" }
      a_lambda.call
      "Outside Lambda"
    end
    
    puts lambda_example
    
    puts '==========='
    
    def proc_example
      a_proc = Proc.new { return "Inside proc" }
      a_proc.call
      "Outside proc"
    end
    
    puts proc_example
    

    The output will be like this:

    Outside Lambda
    ===========
    Inside proc
    

Should we use proc or lambda? I personally prefer lambda because it checks the arguments number strictly.