ruby Method proc lambda 和闭包
2018-03-05 本文已影响14人
风___________
Proc和Lambda的区别,主要是:
- Proc和Lambda都是对象,而Block不是
- 参数列表中最多只能有一个Block,但是可以有多个Proc或Lambda
- Lambda对参数的检查很严格,而Proc则比较宽松
- Proc和Lambda中return关键字的行为是不同的(Proc中外围方法直接返回,Lambda则仅仅是代码块内返回)
- Proc 行为和代码块相似,lambda行为和方法相似
# proc
2.2.0 :094 > def my_func(n)
2.2.0 :095?> b = proc {|item| return item*n }
2.2.0 :096?> end
=> :my_func
2.2.0 :097 > my_func(2).class
=> Proc
2.2.0 :098 > my_func(2).call 5
LocalJumpError: unexpected return
from (irb):95:in `block in my_func'
from (irb):98:in `call'
from (irb):98
from /Users/atpking/.rvm/rubies/ruby-2.2.0/bin/irb:11:in `<main>'
# lambda
2.2.0 :087 > def my_func(n)
2.2.0 :088?> b = lambda {|item| return item*n }
2.2.0 :089?> end
=> :my_func
2.2.0 :090 > my_func(2).call 5
=> 10
2.2.0 :091 > my_func(2).class
=> Proc
2.2.0 :092 >
简述
- ruby 中所有的方法都是真正的方法,即有对象与之相关联,ruby会把无对象关联的方法隐式定义为object对象的私有方法
- methord 对象代表一个方法
- 可用proc和lambda代表一个代码块,他们都是闭包的(proc 行为和代码块一致,lambda和方法行为一致,但是其本质都是表示代码块的对象)
proc
# 1. 显式传递proc对象
def sequence(n, m, b)
b.call(n*m)
end
p = Proc.new {|x| p x}
sequence(5,2,p)
# 2. 方法调用时使用& (如果在一个proc对象前用&,这个调用会想对一个普通代码块一样处理)
# 方法调用中,&只可以被用于修饰方法的最后一个参数,ruby中所有方法都可以传递一个代码块,即使方法不需要代码块也没有yield语句
# &通常出现在一个proc对象前,其实&可以出现在所有支持to_proc方法的对象之前。method类就有此方法,可以跟proc对象一样被传给迭代器
a, b = [1,2,3], [4,5]
summation = Proc.new {|total, x| total+x}
sum = a.inject(0, &summation) # => 6
sum = b.inject(sum, &summation) # => 15
# symbol类也定义了to_proc方法,使得符号也可用&修饰传给迭代器,当一个符号用此方法传递时,他将被假定为一个方法名
2.3.4 :133 > def sum(x,*y)
2.3.4 :134?> total = x
2.3.4 :135?> y.each{|nex| total+=nex}
2.3.4 :136?> total
2.3.4 :137?> end
=> :sum
2.3.4 :138 > sum(1,*[1,2,3])
=> 7
2.3.4 :139 > proc = :sum.to_proc
=> #<Proc:0x007f8e62818f88(&:sum)>
2.3.4 :140 > def sys_print
2.3.4 :141?> yield 1,1,2,3
2.3.4 :142?> end
=> :sys_print
2.3.4 :143 > sys_print &proc
=> 6 # 没弄懂为啥第一个参数没计算进去。。。
2.3.4 :144 > word = ['and','but','car']
=> ["and", "but", "car"]
2.3.4 :145 > uppercase = word.map &:upcase
=> ["AND", "BUT", "CAR"]
2.3.4 :147 > upc = :upcase.to_proc
=> #<Proc:0x007f8e6212ba40(&:upcase)>
2.3.4 :148 > uppercase = word.map &upc
=> ["AND", "BUT", "CAR"]
2.3.4 :149 > uppercase = word.map {|w| w.upcase}
=> ["AND", "BUT", "CAR"]
2.3.4 :150 >
- 方法定义不使用yield关键字时,以&开头的代码块参数,传递的是一个代码块{},&后面的形参是一个proc对象(call是proc对象的方法)
- 方法定义不使用yield关键字时,最后一个参数不是&开头,且最后一个参数可以调用call函数,则为一个proc对象
- 方法调用时,在proc对象前使用&,则proc被当作一个普通代码块使用,&在方法调用时可以被用在所有支持to_proc方法的对象之前,会隐式调用to_proc然后被当作一个普通代码块使用
def makproc(&block)
block # 返回的是proc对象,可以供其他方法使用
end
adder = makeproc{|x,y| x+y} # 创建了一个proc对象
adder.call 2,2 # => 4
1. 创建proc对象的三种方式:
# lambda 字面量
2.3.4 :156 > lambda = ->(x,y){x+y}
=> #<Proc:0x007f8e621e96a8@(irb):156 (lambda)>
2.3.4 :159 > lambda_b = lambda {|x,y| x+y}
=> #<Proc:0x007f8e621aae58@(irb):159 (lambda)>
2.3.4 :157 > proc = proc{|x,y| x+y}
=> #<Proc:0x007f8e621d1cd8@(irb):157>
2.3.4 :158 > proc = Proc.new {|x,y| x+y}
=> #<Proc:0x007f8e621c0848@(irb):158>
# 注意proc{|x,y| x+y}:
# ruby1.8中返回的是Proc(lambda)对象
# ruby1.9中则是Proc.new的简写返回的proc对象
# lambda 同样支持 声明一个与外部同名的块内局部变量的语法
# 这个语法是唯一支持设置代码块参数默认值的语法
# 下面是两个参数和三个块级局部变量的代码块
->(key, value=2; i, j, k){}
lambda字面量的圆括号是可选的如下写法都是成立的:
->x {x+1}
->x, y ;i, j, k {}
->x, y, fac = 2 {}
-> {}
2. 调用Proc 和 lambda
proc_f = Proc.new {|x, y| x+y}
# call 方法调用
proc.f.call 1, 2
# 第二种调用
proc_f[1, 2]
# 第三种调用
proc_f.(1, 2)
lambda
# 字面量定义lambda
lambda = ->(x, y){x+y}
lambda = ->x, y{x+y}
# 调用lambda
lambda.call 2,3
lambda[2,3]
def compose(f, g)
->(x){ f.call(g.call(x)) }
end
succ = compose(->x{x+1}, ->x{x*x})
succ.call 4 # => 17: (4*4)+1
data.sort{|a,b| b-a}
# 重写为
data.sort &->(a,b){ b-a }
proc和lambda是对象而非方法。因此无法像方法一样直接调用,调用封装的代码的方式
2.3.4 :156 > proc = ->(x,y){x+y}
=> #<Proc:0x007f8e621e96a8@(irb):156 (lambda)>
# 第一种 call 函数
2.3.4 :160 > proc.call 1,2
=> 3
# 第二种
2.3.4 :161 > proc[1,2]
=> 3
# 第三种
2.3.4 :162 > proc.(1,2)
=> 3
lambda 使用的一个例子
2.3.4 :163 > def created_lambda(n)
2.3.4 :164?> ->(array){array.collect {|x| x*n}}
2.3.4 :165?> end
=> :created_lambda
2.3.4 :166 > lambda_t = created_lambda 2
=> #<Proc:0x007f8e62821818@(irb):164 (lambda)>
2.3.4 :167 > lambda_t.call([1,2,3])
=> [2, 4, 6]
lambda 闭包的使用实例:
2.3.4 :173 > def accessor_pair(initialValue=nil)
2.3.4 :174?> value = initialValue
2.3.4 :175?> setter = ->(x){value = x}
2.3.4 :176?> getter = ->{value}
2.3.4 :177?> return getter,setter
2.3.4 :178?> end
=> :accessor_pair
2.3.4 :179 > getX,setX = accessor_pair("oldValue")
=> [#<Proc:0x007f8e6208e740@(irb):176 (lambda)>, #<Proc:0x007f8e6208e790@(irb):175 (lambda)>]
2.3.4 :180 > getX.call
=> "oldValue"
2.3.4 :182 > getX[]
=> "oldValue"
2.3.4 :183 > setX["newValue"]
=> "newValue"
2.3.4 :184 > getX[]
=> "newValue"
2.3.4 :185 > def muti(*arge)
2.3.4 :186?> arge.map {|x| lambda{|y| x*y}}
2.3.4 :187?> end
=> :muti
2.3.4 :188 > lam1,lam2 = muti 2,3
=> [#<Proc:0x007f8e622088a0@(irb):186 (lambda)>, #<Proc:0x007f8e62208850@(irb):186 (lambda)>]
2.3.4 :189 > lam1[2]
=> 4
2.3.4 :190 > lam1[3]
=> 6
Method对象
- 方法可以用Method实例表示,但是通过Method调用方法不是很高效
# 创建method对象
# method 和 proc对象相互转化
# method的三个新方法在ruby1.9中
无绑定的Method对象
String.instance_method(:reverse).bind("hello").call
class Module
# instance_method 定义了一个快捷方式
alias [] instance_method
end
# 简写
String[:reverse].bind("hello").call
class UnboundMethod
alise [] bind
end
# 再次简写
String[:reverse]["hello"][]
# 第一个[]索引一个方法 第二个[] 绑定一个对象 第三个[]调用