您当前的位置:首页 > 计算机 > 编程开发 > Other

block、proc、yield、lambda 之间的关系

时间:12-14来源:作者:点击数:

通常我们要定义一个 method 时会写

def say_hello  puts 'hello'end

Functions stand alone and methods are members of a class. A method is a piece of code that is called by a name that is associated with an object.

有时,我们会想要给 method 传参数进去

def multiply(x)  puts x * 2end
pry(main)> multiply(2)=> 4

proc 与 block

当传参数已经无法满足我了,我想要让 method 使用一段程式码( block )时,method的参数加上 &

def myfun(&p)  p.callend

block 是可以暂存一段ruby code的地方,如果只有一行可以用 {...} ,若是很多行可以用 do...end

block 不是物件(object),没有办法单独的存在,也没办法把它指定给某个变数

block 也不是参数(variable), block 通常得像寄生虫一样依附或寄生在其它的方法或物件(或是使用某些类别把它物件化)

ref

& 就像是一个声明,说得是我要传一段 block 进来,然后我用 call 调用传进来的 block

如此一来,我就能在我的method后面,接一个 block把这个 block 给物件化

如此一来,method裡的 p 就是一个物件,可以调用 call 这方法,来取得 block 的内容

pry(main)> myfun { puts "Hello world" }=> Hello worldpry(main)> myfun { puts 2*2 }=> 4

当参数使用 & ,如果直接呼叫 myfun 后面没给 block 就会喷错,因为 myfun 裡的 call 是一个method,method的存在是因为有著一个object来呼叫它。

pry(main)> myfun()=> NoMethodError: undefined method `call' for nil:NilClasspry(main)> myfun=> NoMethodError: undefined method `call' for nil:NilClasspry(main)> myfun() {}=> nil

proc的使用时机

但是 block 的缺点是,他没办法把这段程式码存起来,只有要用时放在method后面。

如果有 想要重複使用的程式码,就用 Proc 包起来Proc 也就是Procedure的意思。

使用 Proc 就可以把 block 物件化

# 原本用block的写法myfun { puts "use myproc" }# 改用Proc写myproc = Proc.new { puts "use myproc" }# 等同于myproc = proc { puts "use myproc" }

Proc 的default method就是 call ,让我们能呼叫要重複使用的程式码

[40] pry(main)> myproc2 = proc { |x| x *2 }=> #<Proc:0x007fde7c666d60@(pry):66>[41] pry(main)> myproc2.call(2)=> 4[42] pry(main)> myproc2.call(3)=> 6

恼人的 &

当method的参数使用 & ,后面又接 Proc

# methoddef myfun(&p)  p.callendmyproc = proc { puts "use myproc" }

执行程式时

pry(main)> myfun(&myproc)=> use myproc# 由于Ruby可以省略括号,所以也可以写成pry(main)> myfun &myproc=> use myproc# 由于我们在myfun这method有用 & 来定义进来的参数,所以myfun这method后面只能接block,# 如果 myfun 后面接 Proc 生成的 object,则会喷错pry(main)> myfun myproc=> ArgumentError: wrong number of arguments (1 for 0)

如果说,我在method的参数,不用 & 则会怎样呢?

def myfun2(p)  p.callendmyproc = Proc.new { puts "use myproc" }pry(main)> myfun2 myproc   # 这就是一般的 Proc 调用 call 方法=> use myprocpry(main)> myfun2 &myproc  # 使用 & 就是在声明使用 block,若是使用block,则 myfun2的参数就该定义 &,由于myfun2的参数没定义 & 所以喷错=> ArgumentError: wrong number of arguments (0 for 1)

如果自定义的method,它的参数不用 & ,而method裡又使用了 call 。则必定要传 Proc 进去。因为 callProc 物件的default method

一个重要的小结论:

当method的定义裡面有用到 call 时,method的参数(变数) p

  • 加上 &&p 代表method后面要接则 block ,透过 &p 使 block 物件化,当 p.call 调用时,就会执行 block
  • 没有 &p 代表要传 proc 这个object进到method裡去,当 p.call 调用时,就会执行传进来的 proc

yield

当我们想要传 block 进入method时,会写

def myfun(&p)  p.callend

如果想要省略掉 &call ,则我们可以使用 yield ,写成

def myfun3  yieldend

换句话说

# 原始写法def myfun(&p)  p.callend# 等同于def myfun3  yieldend

使用 yield

# 能传block进来pry(main)> myfun3 { puts "Hello world" }=> Hello world# 传proc就会喷错myproc = Proc.new { puts "use myproc" }pry(main)>myfun3 myproc=> ArgumentError: wrong number of arguments (1 for 0)

小结: yield = 方便执行block的方式。 yield 语句同等于省略 &block 输入 以及 block.call

lambda

要让method使用一段程式码,除了用 blockProc 之外,也能用 lambda

lambda 的写法很简单,例如:

lambda1 = lambda {|x| x * 2}# 等同于lambda2 = ->(x) { return x * 2 }# 等同于lambda3 = ->(x) { x * 2 }  # Ruby可以省略return

我们先看一下 lambdaProc 的关係

pry(main)> lambda2.class=> Proc

可以看到, lambda 的class就是 Proc 。由于 lambdaProc ,所以 lambda 能调用 Proc 的default method也就是 call

pry(main)> lambda1.call("hello world")=> "hello worldhello world"pry(main)> lambda2.call("hello world")=> "hello worldhello world"pry(main)> lambda3.call("hello world")=> "hello worldhello world"

proc与lambda的差异

lambdaproc 的主要差别有两个

  1. lambda 会check参数数量, proc 不会
  2. return 的处理不同,lambda的 return 只会跳出lambda,proc的 return 会跳出整个method

lambda 会check参数数量, proc 不会

假如我定义 lambda 时只有一个参数,然后丢两个参数进去就会喷错

[3] pry(main)> lam2 = ->(x) {puts  x*2}=> #<Proc:0x007fde7c5d6c60@(pry):3 (lambda)>[4] pry(main)> lam2.call(3)6=> nil[5] pry(main)> lam2.call(3,4)ArgumentError: wrong number of arguments (2 for 1)

但若是 Proc ,只定义一个参数丢两个参数进去,则会只取第一个参数

[6] pry(main)> proc1 = proc {|x| puts x * 2}=> #<Proc:0x007fde7c54b610@(pry):6>[7] pry(main)> proc1.call(2,3,4)4=> nil

换句话说, lambda 对参数的确认较为严谨

return 的处理不同

lambda的 return 只会跳出lambda

#proc and lambdadef run_a_proc(p)  puts 'start...'  p.call  puts 'end.'end#the lambda will be ignoredef run_couple  run_a_proc lambda { puts 'I am a lambda'; return }  run_a_proc proc { puts 'I am a proc'; return }endpry(main)> run_couple=> start...=> I am a lambda=> end.=> start...=> I am a proc=> nil

proc的 return 会跳出整个method

#proc and lambdadef run_a_proc(p)  puts 'start...'  p.call  puts 'end.'end#the lambda will be ignoredef run_couple2  run_a_proc proc { puts 'I am a proc'; return }  run_a_proc lambda { puts 'I am a lambda'; return }endpry(main)> run_couple2=> start...=> I am a proc=> nil

block、Proc、lambda 的小整理

# 使用yield取代 & 与 calldef f1  yieldendf1 { puts "f1" }# 使用blockdef f2(&p)  p.callendf2 { puts "f2" }# 使用proc与lambdadef f3(p)  p.callendf3(proc{ puts "f3"})f3(lambda{puts "f3"})

相关链接

方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
    无相关信息
栏目更新
栏目热门
本栏推荐