Some explanation and examples of Ruby blocks. Encouraged to follow each file in the correct numbered order.
Last active
April 7, 2018 00:28
-
-
Save leandronsp/a6459512a21235a3a87cda20d4099d92 to your computer and use it in GitHub Desktop.
Ruby blocks made easy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Blocks take multiple expressions to be executed later | |
# basic usage | |
greeting = lambda { puts "Hello" } | |
# later | |
greeting.call # => Hello | |
# more variations of block definition | |
greeting = Proc.new { puts "Hello" } | |
greeting = -> { puts "Hello" } | |
# using parameters (different variations, produce same result) | |
greeting = lambda { |name| puts "Hello, #{name}!" } | |
greeting = Proc.new { |name| puts "Hello, #{name}!" } | |
greeting = -> (name) { puts "Hello, #{name}!" } | |
greeting.call('John') # => Hello, John! | |
# from now on, we're gonna use the syntax `-> { }` instead of lambda or Proc.new | |
# some naive calculation | |
sum = -> (n1, n2) { n1 + n2 } | |
sum.call(1, 2) # => 3 | |
multiply = -> (a, b) { a * b } | |
sum.call(2, 3) # => 6 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# print on screen | |
print_on_screen = -> (elem) { puts "Showing element: #{elem}" } | |
print_on_screen.call('something') # => Showing element: something | |
# print on screen all listed elements | |
elements = [1, 2, 3] | |
# the code below just don't care what's inside the block or how it's implemented. | |
# it just calls the block with the element as the argument for the block | |
for element in elements | |
print_on_screen.call(element) | |
end | |
# => | |
# Showing element: 1 | |
# Showing element: 2 | |
# Showing element: 3 | |
# do something else in elements, but could be anything else than printing on screen | |
do_something = -> (elem) { puts "Doing something with: #{elem}" } | |
# note this pattern like in the previous example, the iterator doesn't really need to know what the block does, | |
# just nedded to call the block. | |
# if it's a print on the screen, or writing into a text file, or sending the element over | |
# the network. Just don't care, only execute the block with the argument. | |
for element in elements | |
do_something.call(element) | |
end | |
# => | |
# Doing something with: 1 | |
# Doing something with: 2 | |
# Doing something with: 3 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
list = [1, 2, 3] | |
# lets do something with these elements. | |
# for now we want to use the `for` iteration inside a method, just for the sake of code organization. | |
def iterate_elements(elements) | |
for element in elements | |
# used block here to execute something on each element. | |
# but how can this method execute a block? as far as we know, this method only receives one parameter, | |
# the `elements` array. no blocks here (pun intended) so far... | |
end | |
end | |
# solution: just create the block somewhere before the method definition and pass it over as | |
# the *last* argument prefixed by `&` | |
do_something = -> (elem) { puts "Doing something with: #{elem}" } | |
# define the method | |
def iterate_elements(list, &do_something) | |
for element in list | |
do_something.call(element) | |
end | |
end | |
iterate_elements(elements, &do_something) | |
# => | |
# Doing something with: 1 | |
# Doing something with: 2 | |
# Doing something with: 3 | |
# Our method is receiving any block as the last argument, | |
# then we can create a inline block on the method definition, no need to create it before. | |
iterate_elements(elements) { puts "Hey" } | |
# => | |
# Hey | |
# Hey | |
# Hey | |
iterate_elements(elements) { |n| puts "Hey, #{n}" } | |
# => | |
# Hey, 1 | |
# Hey, 2 | |
# Hey, 3 | |
iterate_elements(['John', 'Alfredo']) { |name| puts "Hello, #{name}" } | |
# => | |
# Hello, John | |
# Hello, Alfredo | |
# blocks in methods can be multi-line as well | |
iterate_elements(['John', 'Alfredo']) do |name| | |
puts "Bye, #{name}" | |
end | |
# => | |
# Bye, John | |
# Bye, Alfredo | |
### ***** GIFT ***** ### | |
# Remeber that Ruby is our friend, so it already did some work that turn our lives easier when mixing methods and blocks. | |
# *Every* method in Ruby is already able to receive optionally a block as the last argument, | |
# but it's implicit and we don't need to specify in the method definition. | |
def iterate_elements(list) # don't need to specify the block in the parameter list | |
for element in list | |
# do_something.call(element) | |
# it won't work, because we no longer have a block parameter called `do_something` to call. | |
# but Ruby is nice and provides a special word called `yield` which basically replaces the `block.call` | |
yield(element) # same as `some_block.call(element)` | |
end | |
end | |
iterate_elements([42]) do |n| | |
puts "The answer to the ultimate question of life, the universe and everything is #{n}" | |
end | |
# => | |
# The answer to the ultimate question of life, the universe and everything is 42 | |
# Ruby's niceness don't stop. Iterating over elements is a common task for any software, | |
# then Ruby provides a method for iteration, called `each`. | |
# pseudo-code, already defined somewhere in the Ruby standard library | |
def each(list) | |
for elem in list | |
yield(elem) | |
end | |
end | |
each([1, 2, 3]) { |n| print "#{n}," } | |
# => 1,2,3, | |
# Actually this method is defined on the class Array or the like. | |
# somewhere inside the Array class: | |
def each | |
for elem in self | |
yield(elem) | |
end | |
end | |
# then we can call it from our array objects | |
[1, 2, 3].each { |n| puts n } | |
# => | |
# 1 | |
# 2 | |
# 3 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# transforming elements in a list | |
list = [1, 2, 3] | |
def iterate_and_transform(list) | |
new_list = [] | |
for elem in list | |
new_list << yield(elem) | |
end | |
new_list | |
end | |
double = iterate_and_transform(list) { |n| n * 2 } # => [2, 4, 6] | |
triple = iterate_and_transform(list) { |n| n * 3 } # => [3, 6, 9] | |
# sum elements in a list | |
def iterate_and_reduce_to_sum(list) | |
sum = 0 # starts with 0 | |
for elem in list | |
# we have to pass over two arguments to the block, so the block can perform the calculation | |
# with the element and the accumulated sum. then reassign the sum. | |
sum = yield(sum, elem) | |
end | |
sum # needs to return the resulted sum | |
end | |
sum = iterate_and_reduce_to_sum([1, 2, 3]) do |accumulated_sum, elem| | |
accumulated_sum + elem | |
end | |
# => 6 | |
# multiply elements in a list | |
def iterate_and_reduce_to_product(list) | |
product = 1 # starts with 1 | |
for elem in list | |
# we have to pass over two arguments to the block, so the block can perform the calculation | |
# with the element and the accumulated product. then reassign the product. | |
product = yield(product, elem) | |
end | |
product # needs to return the resulted product | |
end | |
product = iterate_and_reduce_to_product([3, 2, 2]) { |product, elem| product * elem } | |
# => 12 | |
product = iterate_and_reduce_to_product([1, 2, 3, 0]) { |product, elem| product * elem } | |
# => 0 | |
# Yet to be improved. We are repeating logic on both methods, | |
# (iterate_and_reduce_to_product and iterate_and_reduce_to_sum). | |
# we can refactor to just ONE method that iterates and reduces calculation, | |
# receiving the initial value (accumulated sum or product) on the parameter list | |
def iterate_and_reduce(list, acc) | |
for elem in list | |
acc = yield(product, elem) | |
end | |
acc # returns the final value accumulated | |
end | |
# much better | |
sum = iterate_and_reduce([5, 5, 4]) { |acc, elem| acc + elem } # => 14 | |
product = iterate_and_reduce([2, 4, 1]) { |acc, elem| acc * elem } # => 8 | |
# Even much better. The Ruby-way already defined on the standard library! | |
# transforming elements | |
double = [5, 10].map { |n| n * 2 } # => [10, 20] | |
triple = [1, 2, 3].map { |n| n * 3 } # => [3, 6, 9] | |
sum = [1, 2, 3].reduce(0) { |acc, n| acc + n } # => 6 | |
product = [1, 2, 4, 2].reduce(1) { |acc, n| acc * n } # => 16 | |
# syntax sugar | |
double = [5, 10].map(&2.method(:*)) # => [10, 20] | |
triple = [5, 10].map(&3.method(:*)) # => [15, 30] | |
sum_by_42 = [5, 10].map(&42.method(:+)) # => [47, 52] | |
# no need to specify the initial acc value! | |
sum = [1, 2, 3].reduce(&:+) # => 6 | |
product = [2, 2, 3].reduce(&:*) # => 12 | |
# map and reduce | |
elements = [10, 20, 30, 40, 50] | |
# double each element then reduce to sum | |
elements.map(&2.method(:*)).reduce(&:+) # => 300 | |
# just a variation of code style, returns the same result :) | |
elements | |
.map(&2.method(:*)) | |
.reduce(&:+) | |
# => 300 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment