In Ruby, it is easy to get confused (not good at learning), and the questions often asked in interviews are:
"Please explain what Block, Proc and Lambda are"
"Difference between do... end in Block and curly bracket {}"
"Please explain the difference between Proc and Lambda"
"Why does the Rails scope use Lambda?"
"How can I turn Block into Proc and Lambda?"
"How do you turn Proc and Lambda into blocks?"
The above common problems are met at one time!!
Block (code block)
What is Block?
- Ruby is a quite thorough "Object-Oriented OOP (Object-Oriented Programming)" programming language. Most things are objects, and Block is a few exceptions.
list = [1, 3, 5, 7, 9, 10, 12] list.map { |i| i * 2 } # The map is printed as follows 2 6 10 14 18 20 24 => [2, 6, 10, 14, 18, 20, 24] # Return value # map printed as above list.select { |j| p j.even? } # select is printed as follows false false false false false true true => [10, 12] # Return value # select printed as above list.reduce { |x, y| p x + y } # reduce printed as follows 4 9 16 25 35 47 => 47 # Return value # reduce printed as above list.each do |num| p num * 2 end # each printed as follows 2 6 10 14 18 20 24 => [1, 3, 5, 7, 9, 10, 12] # Return value # each printed as above # It can be seen from the above that the return values of map, select, reduce and each are different # map, select and reduce will return a new array # each loopback receiver
In Ruby, curly braces {} and do... end are blocks, which should be followed by the method and cannot survive alone, otherwise errors will occur.
{ puts "Can't survive alone, an error will occur" } # SyntaxError do puts "Can't survive alone, an error will occur" # SyntaxError end
Block cannot survive alone and will spray error messages. Block is not an object and cannot exist alone.
A Block is just a piece of code attached to a method waiting to be called by the code.
Difference between curly bracket {} and do... end in Block
Generally speaking, {} will be used if it can be written in one line. If it is necessary to write more than one line in case of complex judgment, do... end will be used.
Are there any other differences?
See the following example:
list = [1, 2, 3, 4, 5] p list.map{ |x| x * 2 } # [2, 4, 6, 8, 10] p list.map do |x| x * 2 end # <Enumerator: [1, 2, 3, 4, 5]:map>
It turns out that curly braces {} and do... end have different priorities
Curly braces {} take precedence over do... end
The prototype of the above example is
list = [1, 2, 3, 4, 5] p(list.map{ |x| x * 2 } # [2, 4, 6, 8, 10] p(list.map) do |x| x * 2 end # <Enumerator: [1, 2, 3, 4, 5]:map> # Because the priority is low, it is combined with p first, so the attached blocks will not be processed
How to execute the contents of the Block?
Using the yield method
If you want the attached Block to execute the content, you can use the yield method to temporarily hand over the control right to the Block, and then hand back the control right after the Block execution is completed.
def hi_block puts "Start" yield # Give control to Block for the time being puts "End" end hi_block { puts "Here is Block" } # The printed results are as follows Start Here is Block End => nil # The printing result is as above
Pass parameters to Block
You will find that no matter in the Block of list.map {| i | i * 2} or list.each do |num| p num * 2 end, what are | i | and | num |?
The | enclosing i and num in the Block is called pipe. The intermediate i and num are parameters of the anonymous function, called token. In fact, they are local variables in the Block range. They will become invalid after leaving the Block.
list = [1, 3, 5, 7, 9, 10, 12] list.map { |i| i * 2 } # Variable i is only valid in Block and will generate [2, 6, 10, 14, 18, 20, 24] list.each do |num| p num * 2 end # The variable num is only valid in the Block. It will print 2, 6, 10, 14, 18, 20 and 24 in order puts i # It becomes invalid after leaving the Block, and an error (NameError) occurs in which the variable cannot be found puts num # It becomes invalid after leaving the Block, and an error (NameError) occurs in which the variable cannot be found # Variable name customization will take meaningful names so that people can see and know what it is, rather than taking meaningless i
So, how did i and num come from?
In fact, it's just that when you use the yield method to transfer control to the Block, you bring the value to the Block.
def hi_block puts "Start" yield "Mom, I'm here" # You can also write yield("Mom, I'm here") puts "End" end hi_block { |x| puts "Here is Block,#{x}" } # The printed results are as follows Start Here is Block,Mom, I'm here End => nil # The printing result is as above
- yield can be followed by 1 or more parameters
# Example 1 (with 1 parameter) def hi_block puts "Start" yield 123 # Temporarily give control to Block and pass the number 123 to Block puts "End" end hi_block { |x| # This x is from the yield method puts "Here is Block,I got it. #{x}" } # The printed results are as follows Start Here is Block,I got 123 End => nil # The printing result is as above # Example 2 (with 2 parameters) def tow_parm yield(123, "Parameter 2") end tow_parm { |m, n| puts %Q(I said numbers #{m},You back#{n}~) } # The printed results are as follows When I say the number 123, you go back to parameter 2~ => nil # The printing result is as above
- yield advanced use
Example 1:
i in line 7 is found by yield, and i in line 11, x is the entity variable @v. Print the number in the array by each method.
class Map def initialize @v = [1, 2, 3, 4] end def each_print @v.each { |i| puts yield i } if block_given? end end i = "You'll understand if you look at it a few more times" a_obj = Map.new a_obj.each_print{ |x| "#{i} #{x}" } # The printed results are as follows Read it a few more times and you'll understand 1 Read it a few more times and you'll understand 2 Read it a few more times and you'll understand 3 Read it a few more times and you'll understand 4 => [1, 2, 3, 4] # The printing result is as above
Example 2:
The yield in line 4 takes the counter outside the method to find the Block behind the list. Because the first default is 1, it is known that the counter behind the yield is also 1, which becomes the parameter of | ary | in the external Block. After the Block is executed, return to line 4 and continue to execute.
def list(array, first = 1) counter = first array.each do |item| puts "#{yield counter}. #{item}" counter = counter.next end end list(["a","b","c"]) { |ary| ary * 3 } # The printed results are as follows 3. a 6. b 9. c => ["a", "b", "c"] # The printing result is as above list(["a","b","c"], 100) { |ary| ary * 3 } # The printed results are as follows 300. a 303. b 306. c => ["a", "b", "c"] # The printing result is as above list(["Ruby", "Is", "Fun"], "A") { |ary| ary * 3} # The printed results are as follows AAA. Ruby BBB. Is CCC. Fun => ["Ruby", "Is", "Fun"] # The printing result is as above
Return value of Block
In fact, the yield method temporarily transfers control to the following Block
The execution result of the last line of Block will automatically become the returned value of Block
Therefore, Block can be regarded as the judgment content:
# Example 1 def dog puts "Woof!!Woof!!Woof!!" end dog { puts "You can't see me~~" } # Woof!! Woof!! Woof!! # If there is no yield, the things written in the Block will not react # Example 2 def say_hello(list) result = [] list.each do |i| result << i if yield(i) # If the return value of yield is true end result end p say_hello([*1..10]) { |x| x % 2 == 0 } # [2, 4, 6, 8, 10] p say_hello([*1..10]) { |x| x < 5 } # [1, 2, 3, 4] p say_hello([*1..10]) { |x| return x < 5 } # An error of LocalJumpError will be generated p say_hello([*1..10]) # A LocalJumpError (no block given (yield)) error message will be generated
Above example say_ The Hello method will select the qualified elements according to the setting conditions of the Block. It should be noted that adding return to the Block will cause the error of LocalJumpError, because Block is not a method and it does not know where you want to return.
Block is not a parameter
Block is like a parasite attached to or parasitic on other methods or objects, but it is not a parameter. In the following example, name is the parameter, but block is not.
def say_hello(name) p name end say_hello("side dish") { puts "This is Block"} # "Side dish"
After the above code is executed, there will be no error, but the Block will not be executed.
How to judge whether there is a Block?
There is a situation where there is a yield in the method, but there is no Block when calling the method
def say_hello yield end say_hello # A LocalJumpError (no block given (yield)) error message will be generated
The error message of LocalJumpError (no block given (yield)) will appear.
In this case, the method can be executed normally when it is called
You can use a judgment method block provided by Ruby_ given?
# Example 1 def hi_block if block_given? # Judge whether the execution method is followed by Block yield else "no block" end # The above five lines can be abbreviated as block_given? ? yield : "no block" end hi_block # "no block" hi_block { "hello" } # "hello" hi_block do "hello" end # "hello" # Example 2 def hello_world yield('side dish') if block_given? # Judge whether the execution method is followed by Block # You can also write yield 'side dish' if block_given? end p hello_world # nil hello_world {|x| puts "#{x}" } # side dish # Example 3 def say_hello(name) yield name if block_given? # Judge whether the execution method is followed by Block end say_hello(puts "hi") # hi
Block properties
Summarize the above characteristics
- Not an object, not a parameter
- A piece of code that cannot exist alone, but is attached to a method waiting to be called by code.
- The execution result of the last line of Block will automatically become the returned value of Block
- return cannot be used in a Block
- Cannot be assigned to another object
Although Block is not an object, it cannot exist alone
However, Ruby has two built-in methods to objectify Block and exist separately: Proc and Lamda
Proc
Proc is a program object that can save Ruby code and execute it when necessary, or pass it into other functions as a Block.
After Proc.new is followed by a Block, a Proc object can be generated. After objectification, it is a parameter. Then, you can use the call method to execute the code in the Block.
proc1 = Proc.new { puts "Block Objectified Luo" } # Use the Proc class to objectify a Block # It can also be written as proc2 = Proc.new do puts "Block Objectified Luo" end proc1.call # Block is objectified proc2.call # Block is objectified
return cannot be added to Proc
- Return don't write it in the Block of Proc, otherwise the code will stop running after this section (the execution will end immediately after return), and the code won't go on.
def hi_proc p "strat" hi_proc = Proc.new { return "The execution of this paragraph stops" } hi_proc.call p "end" end p hi_proc # The display results are as follows "strat" "The execution of this paragraph stops" => "The execution of this paragraph stops" # The display result is as above # "end" will not be printed because it stops after executing line 3
Proc with parameters
# Example 1 hi_river = Proc.new { |name| puts "Hello,#{name}"} # It can also be written as hi_river = proc { |name| puts "Hello,#{name}" } hi_river.call("Here's the side dish") # Hello, here's the side dish # Example 2 (with parameters) cal = Proc.new { |num| num * 5 } # It can also be written as cal = proc {| num | num * 5} cal.call(3) # 15 # Example 3 (with parameters) def total_price(price) Proc.new { |num| num * price } # It can also be written as proc {| num | num * price} end n1 = total_price(50) n2 = total_price(30) puts "n1 want #{n1.call(2)} Yuan, and n2 want #{n2.call(5)} yuan“ # n1 costs 100 yuan, while n2 costs 150 yuan
Proc call mode
To execute a Proc object, in addition to the call method, there are several usage methods:
hi_river = Proc.new { |name| puts "Hello,#{name}"} hi_river.call("Here's the side dish") # Using the call method hi_river.("Here's the side dish") # Use parentheses (note that there is one more decimal point after the method) hi_river["Here's the side dish"] # Use square brackets hi_river === "Here's the side dish" # Use three equal signs hi_river.yield "Here's the side dish" # Using the yield method # The above five methods are printed # Hello, here's the side dish
Lambda
- In addition to being converted into Proc, Block can also be converted into Lambda, which is slightly different from Proc:
retrun value
Judgment method of parameters (whether the quantity correctness of parameters will be checked)
How about Proc and Lambda
p1 = Proc.new {|x| x + 1 } p2 = proc {|x| x + 1 } # Another way to write Proc l1 = lambda {|x| x + 1 } l2 = ->(x) { x + 1 } # Another way to write lambda puts "p1: #{p1.lambda?}, #{p1.class}" # p1: false, Proc puts "p2: #{p2.lambda?}, #{p2.class}" # p2: false, Proc puts "l1: #{l1.lambda?}, #{l1.class}" # l1: true, Proc puts "l2: #{l2.lambda?}, #{l2.class}" # l2: true, Proc
Now we know
-
Proc, like Lambda, is a proc object
The above p1, p2, l1 and l2 can be executed using the call method. We can use Lambda? To judge whether it is Lambda. If not, it is Proc. -
return can be added to Lambda
One of the differences between Lambda and Proc is that the return value is different
def hi_lambda p "strat" hi_lambda = lambda { return p "Will continue" } hi_lambda.call p "end" end p hi_lambda # The display results are as follows "strat" "Will continue" "end" "end" => "end" # The display result is as above
Compare the return values of Proc and Lambda once
def test_return(callable_object) callable_object.call * 5 end la = lambda { return 10 } # It can also be written as La = - > {return 10} pr = proc { return 10 } # It can also be written as PR = proc.new {return 10} puts test_return(la) # 50 puts test_return(pr) # Displays the LocalJumpError error error message
Lambda's return is from Lambda return
Proc is defined from the scope return of proc
Speak clearly, do you hear vaguely? Look directly and understand
def test_proc pr = Proc.new { return 10 } result = pr.call return result * 5 end def test_lambda la = lambda { return 10 } result = la.call return result * 5 end puts test_proc # 10 puts test_lambda # 50
test_proc ends retrun on the pr.call line, and test_lambda can complete each line of the method.
Lambda has strict processing parameters
- Proc processing parameters are more flexible, while Lambda is more rigorous
pr = proc { |a, b| [a, b] } # It can also be written as PR = proc. New {a, B | [a, b]} la = lambda { |a, b| [a, b] } # It can also be written as La = - > (a, b) {[a, b]} p pr.call(5, 6) # [5, 6] p pr.call # [nil, nil] p pr.call(5) # [5, nil] p pr.call(5, 6, 7) # [5, 6] p la.call(5, 6) # [5, 6] p la.call # Display ArgumentError error message p la.call(5) # Display ArgumentError error message p la.call(5, 6, 7) # Display ArgumentError error message
Proc will not check the number of parameters. If it is insufficient, nil will be supplemented, and if it is too much, it will be lost automatically. Lambda will require that the number of parameters is correct, which is more rigorous. Otherwise, the ArgumentError error message will be displayed.
Why does Rails scope use Lambda?
Suppose we write a scope that will bring in parameters
scope :product_price, -> (type) { where(price: type) }
If Proc is used, SQL query can still be executed without parameters when Prodct.product_price does not bring in parameters, and will not spray errors, because Proc will preset the parameter value not brought in as nil. When SQL query is equivalent to where(price: nil), you will encounter unexpected conditions, which will be harder to find in Debug.
On the contrary, Lambda can ensure the correctness of the number of parameters. If there are too many or too few parameters, error will tell you not to do so to avoid unnecessary conditions.
This is why the ActiveRecord model in Rails uses Lambda when using scope, because it is more cautious than Proc.
Instead, Lambda behaves more like a common anonymous function.
Use the & symbol to convert Block to Proc and Lambda
Convert Block to Proc and Lambda
In Rails, if we want to find the names of all users from the database and use map, it is written as follows:
# Block to Proc example names = User.all.map { |user| user[:name] } # Form an Array of all names # It can also be written as names = User.all.map(&:name) # Transfer Block to Proc # Block to Proc example pp = Proc.new { |x| puts x * 2 } [1, 2, 3].each(&pp) # Prototype [1, 2, 3]. Each {I | PP [i]} # The printed results are as follows 2 4 6 => [1, 2, 3] # The printing result is as above # Block to Lambda example lam = lambda { |x| puts x * 2 } [1,2,3].each(&lam) # Prototype [1, 2, 3]. Each {I | Lam [i]} # The printed results are as follows 2 4 6 => [1, 2, 3] # The printing result is as above
The strange & symbol represents bringing in a Proc or lambda and converting Block into Proc.
Convert Proc or lambda to Block
One of the uses of & just introduced is to specify to convert from Block to Proc or Lambda at the same time of method declaration. In addition & you can also convert Proc or Lambda to Block:
hi_proc("Hahaha", &proc{ |s| puts s} ) hi_lambda = (1..5).map &->(x){ x*x }
When Proc or Lambda encounters & it will be converted to Block, so the above demonstration meaning is the same as the following:
hi_proc("Hahaha"){ |s| puts s } hi_lambda = (1..5).map { |x| x * x }
&Block is placed at the end of the parameter
The Block cannot know the objectified (parameterized) Block. You need to add &. There can only be one such thing and it must be placed at the end, otherwise a syntax error will occur.
# Error demonstration def hi_block(&p, n) ... end def hi_block(n, &p1, &p2) ... end # Example 1 def hi_block1(str, &test01) "#{str} #{test01.call(18)}" end hi_block1("Hello") { |age| "I'm #{age} years old." } # "Hello I'm 18 years old." # Example 2 def temp_b1 yield("Parameter 1") # Parentheses can be omitted end def temp_b2(&block) block.call("Parameter 2") end block1 = Proc.new {|x| puts "This is Proc #{x}"} block2 = lambda {|x| puts "This is lambda #{x}"} temp_b1 { |x| puts "block0 #{x}" } # block0 parameter 1 temp_b1(&block1) # This is Proc parameter 1 temp_b2(&block2) # This is lambda parameter 2
After reading it, you will find that & in Ruby is very magical, and a lot of things have been done behind it. In fact, it produces a Proc object. Although it is easy to use, if you don't understand the principle behind it, you won't know how to use it and what's wrong.
What if there are more than two & blocks?
def two_block(n, p1, p2) p1[n] # Equivalent to p1.call(n) p2.call n # Parentheses can be omitted end two_block('River', proc { |i| puts "#{i} 1" }, Proc.new { |i| puts "#{i} 2" } ) # The printed results are as follows River 1 River 2 => nil # The printing result is as above
Create a Proc object and write a Block to the Proc.new method when the parameter is passed in. At first glance, it is very lengthy and ugly. This technique is applicable when you want to pass in multiple blocks as parameters at the same time.
Summary
This article is very brain burning. I found a lot of references. I was not very clear at the beginning of writing, but later I could explain it. I felt stronger in the process.