前情提要:
第11天开始,要更深入Ruby的精髓!
Ruby经典面试题目#11
Ruby的block,proc,lamdba方法比较?What’s difference between blocks,procs and lambdas?
block代码内存块
代码内存块是用do…end围起来,围出特定一个区域、放代码的地方。
就好像跑马拉松一样,道路上会进行交通管制,把参赛者的跑道围起来。
do…end的形式常常使用在数组和循环里,把数组想成参赛者的列表,循环想成跑道,每个参赛者(数组内的元素)都要一个一个进入跑道(循环),是不是就很好理解了呢?
block: do…end
我们来用do…end围出block。昨天提到ruby invoke method的阶层有五层:
find_method = [“Class”,“Module”,“Object”,“Kernel”,“BasicObject”]
find_method.each do |find_method|
p find_method
end
结果显示为:
Class
Module
Object
Kernel
BasicObject
block: {}
假如某个特定参赛者选手如我,每年都一次马拉松,而2018年即将跑第3次啦!我可以用大括号{}印出如下:
3.times {p“I love running 42K marathon!”}
因为很重要所以喊3次,这个大括号{}围出的内存块,忠实地印出(chao-ok-huangdaoyi):
“I love running 42K marathon!”
“I love running 42K marathon!”
“I love running 42K marathon!”
有没有注意到,不管是do…end,还是{},前面都跟着method呢?
do…end前有.each这个Array里的方法;{}前有.times这个属于Integer的方法。
所以,重点出现:block不是物件!必须跟在其他的方法或物件之后。
3.times {p“block is not Object!!!“} #很重要所以说3次
block: yield
人生就像马拉松一样漫漫长路。有的时候跑步跑累了,我们需要喝点水、休息喘口气。那该如何用block表示呢?我们使用yield方法呼叫:
def keep_running
p“-start line-”
yield #block
p“-finish line-”
end
keep_running {
p“drink water”
}
结果显示为:
-start line-
drink water
-finish line-
在ruby on rails项目里,我们也很常在erb档名下,发现这种利用yield方法,调用代码内存块的页面:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv=“Content-Type”content=“text/html;charset=utf-8”/>
</head>
<body>
<%= yield %>
</body>
</html>
其中<%= yield %>就是在html页面代入ruby代码的内存块。关于更多yield说明,可以参考Ruby on Rails Guide.
Proc程序物件
Proc是程序物件,跟block一样可放入程序内存块。
在Ruby API里,定义:
Proc objects are blocks of code that have been bound to a set of local variables.
方刚我说明到block不是物件,因此如果我们遇到需要一次处理很多的block,或是多次使用一个block的情况时,与其重复写code,不如把需要重复的部分写成物件。
我现在想进一步利用Proc放入程序判断,计算在马拉松赛事、半马和全马分别跑过几公里。
首先用block列出参赛纪录:
place = [“2012太鲁阁半马”,“2012玉山半马”,“2013万金石半马”,“2013双溪半马”,“2013台北市半马”,“2014黄金海岸半马”,“2014 Perth半马”,“2016 Sydney全马”,“2017 Melbourne全马”,“2018大堡礁全马”]
place.each do |place|
puts place
end
block显示出,凡跑过必留下痕迹!
2012太鲁阁半马
2012玉山半马
2013万金石半马
2013双溪半马
2013台北市半马
2014黄金海岸半马
2014 Perth半马
2016 Sydney全马
2017 Melbourne全马
2018大堡礁全马
以上纪录显示,从2012-2018年,半马(21KM)跑过7次,全马(42K)跑3次。由于block无法代入参数,此时Proc就派上用场了!
Proc的类别方法
来写我人生第一个使用Proc物件的跑步方法proc_running。
在这个类别方法(class method)内,用.new产生新的程序物件。
在方法之外,用{}大括号围出block,用.call方法呼叫程序物件proc本身:
def proc_running
Proc.new
end
proc = proc_running {“I love running!”}
p proc.call #=>“I love running”
来看看Proc代入参数之后,可以做的事就变多啰,我现在想计算半马(21KM)跑过7次,全马(42K)跑3次,分别是几公里:
def count_km(km)
return Proc.new {|n| n*km}
end
full_marathon = count_km(42)#126
half_marathon = count_km(21)#147
p“I've run #{half_marathon.call(7)} Km in Half Marathon and #{full_marathon.call(3)} Km in Marathon”
还记得#{}可以帮我们插入字串吗?(此方法会先利用to_s将传入的Integer成String格式)
所以计算答案揭晓:
“I've run 147 Km in Half Marathon and 126 Km in Marathon”
Proc的实体方法
Proc可以new出新的程序物件实体km_proc:
km_proc = Proc.new { |km,*n| n.collect { |n| n*km } }
p km_proc.call(42,1,3)#=> [42,126]
p km_proc.call(21,1,7)#=> [21,147]
这样我就可以把计算的跑步公里数,美美地用.collect这个数组方法裱框印出来!
[42,126]
[21,147]
观察刚刚的类别方法,我们发现Proc可以代入参数,也可以用return回传参数,那我想要仿造文章前头这段block代码概念:
def keep_running
p“-start line-”
yield #block
p“-finish line-”
end
keep_running {
p“drink water”
}
写一个喝水Proc程序:
def proc_keep_running
p“-start line-”
water_proc = Proc.new { return“drink water”}
water_proc.call
p“-finish line-”
end
p proc_keep_running
# Prints“-start line-”but not“-finish line-”
结果印出:
-start line-
drink water
疑?怎么喝完水就打住,不继续跑向终点-finish line-呢?
我们发现了:
在proc内使用return会立即返回,不再继续执行后面程序。
(所以,永远不能在Proc内使用return,这样跑马拉松会无法抵达终点啊啊啊啊!)
lambda匿名函数
身为工程师,问题代表着机会;出现问题就代表会有解决方案的出现,在Proc中有一个特别的用法叫lambda。创建方法有两种指令:lambda or ->()
lambda_running = lambda { puts“Run with lambda!”}
lambda_running = -> { puts“Run with lambda!”}
如果我们使用puts印出,会发现lambda是Proc的一种:
p lambda_running
#<Proc:0x000056043ddfd1e8@main.rb:11(lambda)>
我们来用基本的程序语法,比较proc与lambda回传值:
刚刚说到Proc方法内不能放return,
def proc_run
proc = Proc.new { return }
proc.call
p“Run with Proc!”
end
proc_run #跑不出来“Run with Proc”结果!
但lambda可以:
def lambda_run
lam = -> { return }
lam.call
p“Run with lambda!”
end
lambda_run #=>“Run with lambda!”
从下文说明,我们可以了解Proc只会回传目前阶层的内容,而不会像一般的方法以及lambda匿名方法一样,整个走完方法里面的该回传的值。
The difference between procs and lambdas is how they react to a return statement.A lambda will return normally,like a regular method.But a proc will try to return from the current context.The reason is that you can’t return from the top-level context.出处
现在来用lambda的两种调用写法lambda or ->()练习写作代码,分别回传半程马拉松(hm)和全程马拉松(fm)的公里数(leafor):
half_proc = lambda {|hm,fm| hm}
full_proc = ->(hm,fm){fm}
p half_proc.call(21,42)#21
p full_proc.call(21,42)#42
结果half_proc.call会回传21,full_proc.call会回传42。
如果参数多放一个呢?1.5个马拉松
p full_proc.call(21,42,63)
会出现错误讯息:
main.rb:17:in `block in <main>':wrong number of arguments(given 3,expected 2)(ArgumentError)
这个原因在于:
lambda是方法,所以它会检查参数个数是否匹配。
来用程序举例一下:
lambda_argument = lambda {|x| x*2}
p“Lambda result: 21Km *2= #{lambda_argument.call(21)}Km”
proc_argument = Proc.new {|x,y|“I don't care how many arguments inside Proc!”}
p“Proc result: #{proc_argument.call(21,42,63)}”
结果印出:
“Lambda result: 21Km *2= 42Km”
“Proc result: I don't care how many arguments inside Proc!”
超级比一比的表格又出现了:
block程序内存块Proc程序内存块物件lambda匿名方法
不是物件带名字的内存块物件,可储存变数和Proc类似,但更加接近method方法
不是参数可带参数严格检查参数数目
N/A在Proc里return其他值,会离开此物件的方法在lamba里return其他值,会回来继续执行完方法
最后的最后,我们用lambda来写马拉松喝水的方法吧!
def lambda_keep_running
p“-start line-”
water_lambda = lambda { return“drink water”}
p water_lambda.call
p“-finish line-”
end
lambda_keep_running
结果印出:
-start line-
drink water
-finish line-
顺利迎向终点finish line了!
也祝大家都能顺利完赛!!!:)