Dissecting Ruby

Enumerable — Implementing Find

Shan Shaji
4 min readJun 8, 2021

Do you ever wonder what is going on behind the scenes when you code? This is something I think about a lot. I am a ruby developer who has depended on different methods in ruby without actually knowing how it is implemented.

I am here to discuss a few of them in detail, or well, dissect some of them. Rather than outline the original C implementation of methods, I’ll try my best to describe it in detail from Ruby’s point of view.

It’s really hard to survive in the Ruby world without using collections and methods on them. One such method is find . It returns the first element which satisfies the given condition in the block. If there is no block, then it returns the enumerator itself.

marks = [40, 60, 34, 70]
marks.find{|a| a < 40}
=> 34
marks_by_subject = {
english: 40, maths: 60, physics: 34, chemistry: 70
}
marks_by_subject.find{|key,value| value < 40}
=> [:physics, 34]
divisible_range = 1..5
divisible_range.find{|num| num % 2 == 0 }
=> 2

Like the find method, there are other common methods every collection has, which originates from the enumerable module. It’s the Ruby way to put the common behavior used by different classes into a module and mixin those modules with classes.

The Enumerable mixin provides collection classes with several traversal and searching methods, and with the ability to sort.

For classes to use the Enumerable module, it has to define an instance method namedeach . The each method is supposed to serve the block with each element. If each method is not defined inside the class, trying to use enumerable methods will throw an NoMethodError .

To serve the block with each element, we need to use yield. yield is a keyword in Ruby which can be used to call the block. The return value of yield is the block’s return value.

class Week
include Enumerable
def each
yield "Monday"
yield "Tuesday"
yield "Wednesday"
yield "Thursday"
yield "Friday"
yield "Saturday"
yield "Sunday"
end
end
week = Week.new
=> #<Week:0x0000559fe6b79a40>
week.include?("Friday")
=> true
week.find {|day| day.start_with?('Sat') }
=> "Saturday"

Even though we have not defined the methods like include? or find in the class Week, the Enumerable module and each method helps us to achieve this function.

To understand this better, I thought of implementing the find and each method in ruby.

I opened the array class and created my version of each,my_each . It’s a simple method which goes through each element in an array by index with the help of a while loop and serves it to the block.

class Array
def my_each
return to_enum(:my_each) unless block_given?
i = 0
while i < size
yield self[i]
i += 1
end
self
end
end
marks = [40, 60, 77, 81, 67]
marks.my_each{|mark| p mark} # will print all elements in array

It’s a common practice to return an Enumerator when they’re called without a block. Enumerators can be used to chain methods. In simple, Enumerators are similar to iterators but instead of being a method, it’s an object with a state that can track its own progress. Talking about Enumerators could be an article in itself, so lets not get into that now.

The next step is to create a myfind method which will get a value from the each method. This value is then passed to the block which is provided to the my_find method. If the block returns true, we break from each and return the element found.

module MyEnumerable
def my_find failure = nil
return to_enum(:my_find) unless block_given?
element_found = nil
is_found = false
each do |num|
if yield num
element_found = num
is_found = true
break
end
end
unless (is_found || failure.nil?)
failure.call
else
element_found
end
end
end
class Array
include MyEnumerable
end

This code has created a MyEnumerable module which has a myfind method that is included in the Array class. The optional failure argument in the myfind method will be a lambda/proc object which will be called if the find operation fails.

failure = lambda { "No Distinction" }marks = [40, 60, 77, 81, 67]marks.my_find(failure) {|mark| mark > 90 }
=> "No Distinction"
marks.my_find{|mark| mark.equal? 77 }
=> 77

It’s not just arrays, we can use the myfind method we created in all the classes which have an each method.

class Hash
include MyEnumerable
end
marks_by_subject = {
english: 40, maths: 60, physics: 34, chemistry: 70
}
marks_by_subject.my_find{|key,value| value < 40}
=> [:physics, 34]
class Week
include MyEnumerable
end
week.my_find {|day| day.start_with?('Sat') }
=> "Saturday"

We can create all the Enumerable methods on top of each method like this.

By creating your own find and each methods, we’re able to recognize that under the hood, all Enumerable methods use the each method. Please let me know what you think, your feedback is appreciated.

--

--

Shan Shaji
Shan Shaji

Written by Shan Shaji

End to End Product Developer with an immense passion for Rails and React. https://github.com/shanshaji

No responses yet