Many people like Ruby for its beauty and flexibility. But some do not realize the dark side of the Ruby language. Today we will examine one of the dark horses of the Ruby, many use it, but not many know its secret. So, the topic of the day blocks!
Blocks in ruby are used almost everywhere, often they replace methods. But, there are significant differences between the method and the block.
The first difference:
Blocks see variables declared in the block scope, but methods do not.
Example using blocks:
hello = "Hello World"
l = lambda {puts hello}
l.call #=> Hello World => nil
Example using the method:
def say_hello
puts hello
end
hello = "Hello World"
say_hello #=> NameError: undefined local variable or method `hello'
As you can see, the method does not see the variable until you pass it as an argument.
')
The second difference:
Sensitivity of methods and blocks to accepted arguments. Methods are very sensitive to arguments, you can pass only the number of arguments that are described in the method. Otherwise, you will get an error instead of a result.
With blocks, the problem is simpler and more difficult at the same time, in Ruby there are two types of blocks, Proc.new {} and lambda {}, each of them reacts differently to the arguments.
p = Proc.new { |i, k| [i, k] }
l = lambda { |i, k| [i, k] }
p.call("hello", "world") # => ["hello","world"]
p.call("hello") # => ["hello, nil]
p.call # => [nil, nil]
p.call("hello", "world", "bye") # => ["hello", "world"]
l.call("hello", "world") # => ["hello", "world"]
l.call("hello") # ArgumentError: wrong number of arguments (1 for 2)
l.call # ArgumentError: wrong number of arguments (0 for 2)
l.call("hello", "world", "bye") # ArgumentError: wrong number of arguments (3 for 2)
Vrodeba is understandable, even if such a difference annoys a bit (it would seem the same block), you can get used to it, but these are just flowers! :)
In the previous example, we considered blocks that can have 2 or more arguments. Logically, nothing should change if there is 1 or no arguments at all. But since life is complicated and dishonest, so Ruby has his own view on logic.
p = Proc.new { |i| i }
l = lambda { |i| i }
p.call("hello") # => "hello"
p.call("hello", "world") # => ["hello", "world"] # warning: multiple values for a block parameter (2 for 1)
p.call # => nil # warning: multiple values for a block parameter (0 for 1)
l.call("hello") # => "hello"
l.call("hello", "world") # => ["hello", "world"] # warning: multiple values for a block parameter (2 for 1)
l.call # => nil # warning: multiple values for a block parameter (0 for 1)
How do you like this unusual turn of events? From the previous example, the novice realized that there were 2 different blocks, and both of them react differently to the arguments, but after this example everything turned upside down, now, instead of an error, the novice received a warning. Any person who has just started to learn this part of the language will get the brain loaded and freeze. But Matz and here does not relax our brains. Go ahead:
p = Proc.new { puts " " }
l = lambda { puts " " }
p.call # => nil
p.call("hello") # => nil
l.call # => nil
l.call("hello") # => nil
The final touch in breaking all patterns and rules of logic. NLP trainings teach how to break a pattern to gain access to a person’s subconscious, this is one such example. In the absence of arguments, both types of blocks react in the same way.
You can sum up a little bit. There are 2 types of work with arguments in Ruby - strict and indefinite (strict & loose). Methods always work on the principle of strict arguments, Proc blocks work on the principle of undefined arguments, but lambda blocks work in strict mode if there are more than 2 arguments and in indefinite mode if there are no arguments or there is one single argument.
There is another difference between lambda and proc blocks. This difference lies in return.
lambda blocks simply return the result from the block, for example:
def max_element
l = lambda { return [1,2,3,4] }
array = l.call
return array.max
end
max_element # => 4
And Proc works a little differently, instead of simply returning the result from the block, Proc returns the result in its declared zone, for example:
def max_element
p = Proc.new { return [1,2,3,4] }
array = p.call
return array.max # Proc
end
max_element # => [1,2,3,4]
As you can see, it never came to return array.max, since the p block caused return to this.
Ruby is a wonderful language, which provides great opportunities for creativity, but it has its own dark sides that you need to know :)