Blocks
Blocks are chunks of code that can be passed around. We can’t find blocks just hanging around without some method. It doesn’t mean anything on a standalone basis and can only appear in argument lists.
You declare a block using braces {} if it’s on one line or do … end if it’s on multiple lines. This is just a convention followed in the Ruby community.
['s','a','i'].each { |chr| print "#{chr}" }
Blocks with input
Blocks can take inputs and can also return expressions. Blocks lets use the implicit return (whatever’s on the last line) and not the return
keyword, since that will return us from whatever method actually called the block.
Blocks are just arguments
Blocks are used as arguments to other methods. They are nothing special, just plain arguments. They are listed last and on their own because they tend to take up multiple lines. Don’t think of them as anything too special.
yield
yield
basically says “run the block right here”. yield can pass parameters to your block as well.
each method
Lets write the Ruby code for the each
method on Arrays to get an idea of what’s happening behind the scenes.
class Array
def each
i = 0
while i < self.size
yield(self[i])
i+=1
end
self
end
end
['s','a','i'].each { |chr| print "#{chr}!" }
Check if block was passed?
To check if block was passed at all(to only yield in that case): yield if block_given?
Passing multiple blocks
Can we pass multiple blocks to a function? Can we save a block and use it again later? Thats where the incredible procs(Procedures) comes into picture. Actually, a block is a Proc. The block can be imagined as a subclass of a Proc.
Procs
A Proc can be saved to a variable and reused again unlike blocks.
p = Proc.new { |x| puts x }
[1,2,3].each(&p)
# The '&' tells ruby to turn the proc into a block
Call
We use call
for Procs instead of yield
. This is quiet intuitive as we know that a function can accept multiple procs unlike a single block so yield
in this case doesn’t mention which Proc has to be called. call
literally just runs the Proc that is called on. It can take arguments as well.
p.call(“sailesh”)
Differences between Blocks and Procs
Procs are objects, blocks are not. A proc is an instance of the Proc class. They can be assigned to variables. Procs can also return themselves.
p = Proc.new { puts "Welcome!" }
p.call # prints 'Welcome'
p.class # returns 'Proc'
a = p # a now equals p, a Proc instance
p # returns a proc object id
In contrast, a block is just part of the syntax of a method call.
{ puts "Welcome!"} # syntax error
a = { puts "Welcome!"} # syntax error
['s','a','i'].each { |chr| print "#{chr}!" } # no error
We can pass multiple procs to methods.
def funct(proc1, proc2)
proc1.call
proc2.call
end
p1 = Proc.new { puts "proc 1" }
p2 = Proc.new { puts "proc 2" }
funct(p1,p2)
Lambdas
A lambda acts more like a real method. A lambda gives us more flexibility with what it returns (like if you want to return multiple values at once) because we can safely use the explicit return statement inside of one. With lambdas, return will only return from the lambda itself and not the enclosing method, which is what happens if you use return inside a block or Proc. Lambdas are also much stricter than Procs about passing them the correct number of arguments.
lambda do |arg|
puts arg
return arg # you can do this in lambdas not Procs
end.call("sailesh")
#we are chaining the call method on the lambda
Differences between Procs and Lambdas
Its important to note that they are both Proc objects.
proc = Proc.new { puts "Welcome!" }
lam = lambda { puts "Welcome!" }
proc.class # returns 'Proc'
lam.class # returns 'Proc'
Lambdas check the number of arguments, while procs do not
lam = lambda { |x| puts x } # creates a lambda that takes 1 argument
lam.call(5) # prints out 5
lam.call # ArgumentError: wrong number of arguments (0 for 1)
lam.call(7,8,9) # ArgumentError: wrong number of arguments (3 for 1)
Procs don’t care if they are passed the wrong number of arguments.
proc = Proc.new { |x| puts x } # creates a proc that takes 1 argument
proc.call(5) # prints out 5
proc.call # returns nil
proc.call(7,8,9) # prints out 7 and forgets about the extra arguments
return
inside of a lambda triggers the code right outside of the lambda code
Closures
The term closure is often mistakenly used to mean anonymous function. This is probably because many programmers learn about both concepts at the same time, in the form of small helper functions that are anonymous closures. An anonymous function is a function literal without a name, while a closure is an instance of a function, a value, whose non-local variables have been bound either to values or to storage locations.
Proc objects preserving local context
def counter
n = 0
return Proc.new { n+= 1 }
end
a = counter
a.call # returns 1
a.call # returns 2
b = counter
b.call # returns 1
a.call # returns 3
Methods
This is very different from an actual method. They are a convenient way to pass a normal method to another normal method by wrapping the symbol of its name in the word method()