Skip to content

Instantly share code, notes, and snippets.

@leandronsp
Last active April 7, 2018 00:28
Show Gist options
  • Save leandronsp/a6459512a21235a3a87cda20d4099d92 to your computer and use it in GitHub Desktop.
Save leandronsp/a6459512a21235a3a87cda20d4099d92 to your computer and use it in GitHub Desktop.
Ruby blocks made easy

Ruby blocks

Some explanation and examples of Ruby blocks. Encouraged to follow each file in the correct numbered order.

# 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
# 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
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
# 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