分享 聊聊 Ruby 中的 block, proc 和 lambda

kayakjiang · 2013年04月22日 · 最后由 bluefish733 回复于 2019年05月07日 · 36425 次阅读
本帖已被管理员设置为精华贴

做为热身,从一些简单的例子开始,

def f1
  yield
end

def f2(&p)
  p.call
end

def f3(p)
  p.call
end

f1 { puts "f1" }

f2 { puts "f2" }

f3(proc{ puts "f3"})

f3(Proc.new{puts "f3"})

f3(lambda{puts "f3"})

若你用的是 ruby1.9 及以上的版本,还可以这样,

f3(-> {puts "f3"})

上面是 block, proc 和 lambda 的一些基本用法。

block

先说说 block, ruby 中的 block 是方法的一个重要但非必要的组成部分,我们可以认为方法的完整定义类似于,

def f(零个或多个参数, &p)
  ...
end

注意&p 不是参数,&p 类似于一种声明,当方法后面有 block 时,会将 block 捕捉起来存放到变量 p 中,如果方法后面没有 block,那么&p 什么也不干。

def f(&p)
end

f(1)  #=> 会抛出ArgumentError: wrong number of arguments (1 for 0)异常

f()  #=> 没有异常抛出

f() { puts "f"} #=> 没有异常抛出

从上面代码的运行结果可以知道&p 不是参数

def f(a)
  puts a
end

f(1) { puts 2}  #=> 没有异常抛出,输出1

所以任何方法后面都可以挂载一个 block,如果你定义的方法想使用 block 做点事情,那么你需要使用 yield 关键字或者&p

def f1
  yield
end

def f2(&p)
  p.call
end

此时 f1, f2 执行时后面必须挂一个 block,否则会抛出异常,f1 抛出 LocalJumpError: no block given (yield) 的异常,f2 抛出 NoMethodError: undefined method 'call' for nil:NilClass 的异常,ruby 提供了block_given?方法来判断方法后面是否挂了 block,于是我们可以这样修改 f1 和 f2,

def f1
 yield if block_given?
end

def f2(&p)
  p.call if block_given?
end

这样的话,f1 和 f2 后面无论挂不挂 block 都不会抛异常了。 我们再来看看 f2 修改前抛出的 NoMethodError: undefined method 'call' for nil:NilClass 异常,这种说明当 f2 后面没有挂 block 的时候 p 是 nil,那么我们给 f2 挂个 block,再打印出 p,看看 p 究竟是什么,

def f2(&p)
  puts p.class
  puts p.inspect
  p.call
end

f2 {} # 输出Proc和类似<Proc:0x007fdc72829780@(irb):21>

这说明 p 是一个 Proc 实例对象,在 ruby 中,&还可以这么用,[1,2] & [2,3] 或者 puts true if 1 && 1 或者在某个类中将它作为一个方法名。

很多 ruby 老鸟会写类似下面的代码,

["1", "2", "3"].map(&:to_i),其效果和["1", "2", "3"].map {|i| i.to_i }一样,但简洁了许多,并且更加拉风。 这里的魔法在于符号&会触发:to_i 的 to_proc 方法,to_proc 执行后会返回一个 proc 实例,然后&会把这个 proc 实例转换成一个 block,我们需要要明白 map 方法后挂的是一个 block,而不是接收一个 proc 对象做为参数。&:to_i 是一个 block,block 不能独立存在,同时你也没有办法直接存储或传递它,必须把 block 挂在某个方法后面。

:to_i 是 Symbol 类的实例,Symbol 中的 to_proc 方法的实现类似于,

class Symbol

  def to_proc
    Proc.new {|obj| obj.send(self) }
  end

end

同理我们可以给自己写的类定义 to_proc 方法,然后使用&耍下酷,比如,

class AddBy

  def initialize(num = 0)
    @num = num
  end

  def to_proc
    Proc.new {|obj| obj.send('+', @num)}
  end

end

add_by_9 = AddBy.new(9)
puts [1,2,3].map(&add_by_9) #输出 [10, 11, 12]

在 ruby 中,block 有形,它有时候是这样

do |...|
  ...
end

有时候是这样

{|...| ...}

或者类似 &p, &:to_i, &add_by_9 之类,但是它无体,无体的意思就是说 block 无法单独存在,必须挂在方法后面,并且你没有办法直接把它存到变量里,也没有办法直接将它作为参数传递给方法,所以当你想存储,传递 block 时,你可以使用 proc 对象了,

 p = Proc.new(&:to_i)
 p = Proc.new {|obj| obj.to_i } 
 p = Proc.new do |obj|
   obj.to_i
 end
 p = proc(&:to_i)
 p = proc {|obj| obj.to_i}
 p = proc do |obj|
   obj.to_i
 end

def make_proc(&p)
  p
end

p = make_proc(&:to_i)
p = make_proc do |obj|
  obj.to_i
end
p = make_proc {|obj| obj.to_i }

虽然我在开发中经常用到 block,但是我很少显式地去使用 Proc 或 proc 去实例化 block,比如我几乎没有写过这样的代码,

f(Proc.new {|...| ...})

f(proc {|...| ...})

p =  Proc.new {|...| ...} #然后在某个地方p.call(...)或者将p传递给某个方法,比如f(p)

在使用 block 时,我会忽略 proc 的存在,我将 proc 定位为一个幕后的工作者。我经常写类似下面的代码,

def f(...)
  ...
  yield
  ...
end

def f(..., &p)
  ...
  p.call
  ...
end

def f(..., &p)
  instance_eval &p
  ...
end

def f(..., &p)
  ...
  defime_method m, &p
  ...
end

有些新手会写类似下面的一执行就会报错的代码,

def f(..., &p)
  instance_eval p
end

def f(..., p)
  instance_eval p.call
end

也有这样写的,

def f(..., &p)
  instance_eval do
    p.call
  end
end

或者

def f(...)
  instance_eval do
    yield
  end
end

我甚至写过类似下面的代码,

def f(...)
  instance_eval yield
end

我们经常在该挂 block 的时候,却把 proc 对象当参数传给方法了,或者不明白&p 就是 block 可以直接交给方法使用,我曾经也犯过这样的错误就是因为没有把 block 和 proc 正确的区分开来,&p 是 block, p 是 proc,不到万不得已的情况下不要显式地创建 proc,每当我对 block 和 proc 之间的关系犯糊涂时,我就会念上几句。

再来聊聊 yield 和&p,我们经常这样定义方法,

def f(...)
  yield(...)
end

def f(..., &p)
  p.call(...)
end

yield 和 call 后面都可以接参数,如果你是这样定义方法

def f(...)
  yield 1, 2
end

那么可以这样执行代码,

f(...) do |i, j|
  puts i
  puts j
end

但是这样做也不会有错,

f(...) do
  ...
end

p.call(...) 的情况类似,也就是说 block 和 proc 都不检查参数 (其实通过 proc 方法创建的 proc 在 1.8 是严格检查参数的,但是在 1.9 及以上的版本是不检查参数的),为什么 block 和 proc 不检查参数呢?其实这个很好理解,因为在实际应用中你可能需要在一个方法中多次调用 block 或者 proc 并且给的参数个数不一样,比如,


def f()
  yield 0
  yield 1, 2
end

def ff(&p)
  p.call 0
  p.call 1, 2, 3
end

f do |a1, a2, a3|
  puts a1   
  puts a2
   puts a3
end

由于方法后面只能挂一个 block,所以要实现上面的代码功能,就不能去严格检查参数了。

转入正题,这两种方式效果差不多,都能很好地利用 block。使用 yield,看起来简洁,使用&p,看起来直观,并且你可以将&p 传给其他方法使用。 但是在 ruby1.8 的版本你不应像下面这样做,

def f1(...)
  eval("yield")
end

可以,

def f2(..., &p)
  eval("p.call")
end
f1(...) {}  # 会抛异常
f2(...) {}  # 正常运行

当然上面这种用法很少见,但是却被我碰到了,我经常写一些方法去请求外部的 api,有时这些外部的 api 不是特别稳定,时不时会遇到一些 bad respoense, timeout 错误,针对这些错误,应该立即重发报文重试,对于其他异常就直接抛异常。于是我写了一个 try 方法来满足这个需求,


  def try(title, options = { }, &p)
    ee = '#{e}'
    tried_times = 0
    max_times = options[:max_times] || 3
    exceptions = options[:on] || Exception
    exceptions = [exceptions] if !exceptions.is_a?(Array)
    rescue_text = <<-EOF
      begin
        p.call
      rescue #{exceptions.join(',')} => e
        Rails.logger.info("#{title}发生异常#{ee}")
        if (tried_times += 1) < max_times
          Rails.logger.info("开始重试#{title}--第#{tried_times}次重试")
          retry 
        end
        raise e
      end
    EOF
    eval rescue_text
  end

try('某某api', :max_times => 2, :on => [Net::HTTPBadResponse, Timeout::Error]) do
  open(api_url)
end

最开始我用的是 yield,结果在 ree 下执行 try 方法时会报错,后来改成使用&p 就通过了。

通过试验发现在 ruby1.9 及以上版本已经没有这种差异了。

做个小结, block 和 proc 是两种不同的东西,block 有形无体,proc 可以将 block 实体化,可以把&p 看做一种运算,其中&触发 p 的 to_proc 方法,然后&会将 to_proc 方法返回的 proc 对象转换成 block

其中 proc 对象的 to_proc 方法返回自身。

p = proc {}
p.equal? p.to_proc  # 返回true

lambda

lambda 是匿名方法,lambda 和 proc 也是两种不同的东西,但是在 ruby 中 lambda 只能依附 proc 而存在,这点和 block 不同,block 并不依赖 proc。

l = lambda {}
puts l.class 

ruby1.8 中输出的信息类似#Proc:0x0000000000000000@irb):1( 在 ruby1.9 及以上版本输出的信息类似#Proc:0x007f85548109d0@irb):1lambda)(,注意 1.9 及以上版本的输出多了 (lambda),从这里可以理解 ruby 的设计者们确实在有意的区分 lambda 和 proc,并不想把 lambda 和 proc 混在一起,如同 ruby 中没有叫 Block 的类,除非你自己定义一个,ruby 中也没有叫 Lambda 的类,于是将 lambda 对象化的活儿就交给了 Proc,于是令人头大的情况出现了,当你用 lambda 弄出了一个匿名方法时,发现它是一个 proc 对象,并且这个匿名方法能干的活,proc 对象都能做,于是我们这些码农不淡定了,Proc.new {}这样可以,proc {}这样也没有问题,lambda {}这样做也不错,->{}这个还是能行,我平时吃个饭都为吃什么左右为难,现在一下子多出了四种差不多的方案来实现同一件事情,确实让人不好选择,特别是有些码农还有点小洁癖,如果在代码里一会儿看到proc{}, 一会儿看到lambda{},这多不整洁啊,让人心情不畅快。在这里我们认定 lambda 或者 -> 弄出的就是一个匿名方法,记做 l, 即使它披着 proc 的外衣,proc 或者 Proc.new 创建的就是一个 proc 对象,记做 p 在 ruby 各个版本中,lp 是有一些差别的。

定义一些方法,


def f0()
  p = Proc.new {return 0}
  p.call
  1
end

def f1()
  p = proc { return 0 }
  p.call
  1
end

def f2()
  l = lambda { return 0}
  l.call
  1
end

def f3(p)
  instance_eval &p
  1
end

class A
end

def f4(p)
  A.class_eval &p
  1
end

p1 = Proc.new { }
p2 = proc {}
l  = lambda {}

def f5()
  yield 1, 2
end

f5 {}
f5 {|i|}
f5 {|i,j|}
f5 {|i,j,k|}

lambda 和 proc 之间的区别除了那个经常用做面试题目的经典的 return 之外,还有一个区别就是 lambda 不能完美的转换为 block(这点可以通过 f3 和 f4 执行的过程得证),而 proc 可以完美的转换为 block,注意,我说的 lambda 指的是用 lambda 方法或者->符号生成的 proc,当然和方法一样 lambda 是严格检查参数的,这个特点也和 proc 不一样。

从上面的数据对比来看,在 1.8 版本,lambda 和 proc 方法生成的 proc 对象的行为是一致的,但在 1.9 以上和 jruby 的版本中,lambda 和 proc 的不同处增多,可以认为 ruby 的设计者们并不想把 lambda 和 proc 混同为一件事物。如我前面所讲,proc 的主要作用是对象化 block 和 lambda,并且 proc 在行为上更接近于 block。

retrun 的几个试验


def f0()
  p = Proc.new { return 0}
  p.call
  1
end

def f1()
  l = lambda { return 0}
  l.call
  1
end

f0 # 返回0
f1 # 返回1

如果你能够理解 proc 在行为上更像 block,lambda 其实就是方法只不过是匿名的,那么你对上面的结果不会感到惊讶。

如果把 f0,f1 做一些修改,就更容易理解上面的结果了。


def f0()
  return 0
  1
end

def f1()
  def __f1
    return 0
  end
  __f1
  1
end

f0 # 返回0
f1 # 返回1

return 只能在方法体里执行


p = Proc.new { return 0 }
l = lambda { return 0 }

p.call # 报LocalJumpError
l.call # 返回0

构造 p 的时候我没有使用 proc {return 0},因为在 ruby1.8 中,proc {return 0}的行为和 lambda 一样,比如在 ruby1.8 中,


def f(p)
  p.call
  1
end

p = proc { return 0 }
l = lambda { return 0 }

f(p)  # 返回1
f(l)  # 返回1

p.call # 返回0
l.call # 返回0

def f(p)
  p.call
  1
end

p = Proc.new { return 0 }
l = lambda.new { return 0}

f(p)  # 报LocalJumpError
f(l)  # 返回1

我感觉 proc 中的 return 能记住 proc 生成时其 block 的位置,然后无论 proc 在哪里调用,都会从 proc 生成时其 block 所在位置处开始 return,有下面的代码为证:


def f(p)
  p.call
  1
end

def f2()
  Proc.new { return 0 }
end

def f3()
  lambda { return 0 }
end

p = f2()
l = f3()

f(p)  # 报LocalJumpError: unexpected return,from (irb):29:in `f2'
f(l)  # 返回1

我在 1.8, ree, jruby, 1.9, 2.0 各版本都测试过,结果一样。

再看看下面的代码,


def f0(&p)
  p.call
  1
end

def f1()
  yield
  1
end

f0 { return 0}  # LocalJumpErrorw
f1 { return 0}  # LocalJumpErrorw

f0(&Proc.new{return 0}) # LocalJumpError
f0(&lambda{return 0})   # 返回1

def f2(&p)
  p
end

def f3()
  p = f2 do
    return 0
  end
  p.call
  1
end

f3 # 返回0

我们重点看看 f3 中的 proc p,它虽然是从 f2 中生成返回的,但是 p 生成时其 block 是处在在 f3 的方法体内,这个类似于,


def f3()
  p = Proc.new { return 0}
  p.call
  1
end

所以执行 f3 时,没有异常抛出,返回 0。

总结

当你想写出类似于 f do ...; end 或者 f {...}的代码时,请直接使用 block,通过 yield, &p 就能达到目的,当你想使用 proc 时,其实此时绝大部分的情况是你实际想用 lambda,请直接使用 lambda{}或者->{}就可以了, 尽量不要显示地使用 Proc.new{} 或者 proc{}去创建 proc。

废话说了一大堆,其实我最想说的是:用 block,用 lambda,不要用 proc,让 proc 做好自己的幕后工作就好了

👍 Proc 里写 return 会把 call Proc 的方法直接 return 掉是一个很奇怪的设计,一直被我认为违反了代码结构化。

#1 楼 @iBachue Proc 中的 ruturn 之所以如此表现是为了和 block 保持一致,所以我们平时写代码不要去碰 Proc 这颗雷,因为它形式上像 lambda,里子却是 block

最近也有看这部分的知识,不过还是学不透彻,有些东西混淆了⋯⋯

好长好长呀~~! 有机会一定细细品读。

讲的很清楚,就是略长~

《Ruby 编程语言》的 第 6 章 方法、Proc、Lambda 和闭包 对这些概念做出了非常好的阐释。有兴趣的可以看看。其实这几个东西主要的区别就是编程里最让人头大的一个概念 作用域

另外感觉最近 瞎扯淡 的帖子越来越多,想这样研究技术的应该多鼓励,真心想多加几个红心,但我只能加一个。

@lgn21st @huacnlee @Rei 各位,是不是应该弄个精品贴聚合的功能,把这些技术贴按类别和版本收集起来。要不过几天就又被淹没了,这种帖子是有保留价值的。

首先赞 lz 的讲解。

但是不能理解为什么会有最后的结论。

用 block,用 lambda,不要用 proc,让 proc 做好自己的幕后工作就好了。

我觉得用哪个取决于你要做什么,根据上面你讲的他们不同的特性去选择,若没有严格的限制的话,用哪个都没问题,除非希望代码风格上保持一致,可以都选择一种,但是没有所谓的 最好用哪个,最好别用哪个的 说法。

Sinatra 的代码中绝大多数,不,应该是所有 proc 的写法都是显式的使用 Proc.new.

支持弄个精品聚合,像上次那个删帖就很棒,只是希望楼主不要轻易删帖。

权限可设置一旦成为精品,任何人就无法删除。

很详细的对比解释,终于了解 ['1','2'].map(&:to_i) 的原理

哈哈,好久不见啊。

每次我用的时候,都是想到下面这关系 &proc => block

#8 楼 @zgm 你说的对,sinatra 中确实都是用的是 Proc.new,他这样做看起来更面向对像些,他里面的很多配置是类似这样写的

set :raise_errors, Proc.new { test? }

这是可以用 lambda 代替的,sinatra 做的比较好的一点就是始终如一的用 Proc.new 统一了风格。 看待问题的角度不同,处理问题的手段也就不一样了,我把 Proc 的主要功能定位于在幕后对象化 block 和 lambda,所以在平时写代码的时候一般不用 Proc.new,当然到了非要用 Proc.new 的时候,也就用了。

#12 楼 @wxianfeng 好啊,我也是这么想的

#6 楼 @hpyhacking 帖子的很多内容在《Ruby 编程语言》都有讲到,我算是做了一次搬运工,然后做了些试验

#6 楼 @hpyhacking 我从《Ruby 元编程》里学到的最大的体会就是:class, module, def, proc, lambda 等都是定义作用域的思想确实让人耳目一新。

#10 楼 @reyesyang 这个部分似乎没有楼主讲得这么简单 ['1','2'].map(&:to_i)里还蕴含了很多奇怪的地方恐怕只能通过阅读 Ruby 源码来了解了

这个总结好。

我一直的笨办法就是记住:block 就是一段代码,call 的时候就是当前位置嵌入了代码;lambda 相当于一个方法,call 的时候就是方法调用

#20 楼 @ghjcumt2008 这么一说就更方便记了😄

@kayakjiang @iBachue 我现在知道了一般情况下应该 用 block,用 lambda,不要用 proc,让 proc 做好自己的幕后工作就好,但是怎么解释 Rails Guide 3.2.13 里面推荐使用 Proc,而不是 lambda? http://guides.rubyonrails.org/active_record_validations_callbacks.html#using-a-proc-with-if-and-unless

#23 楼 @ericguo 1. 例子中说并没有它推荐 Proc 啊

  1. 其实这里它强调的是 Proc,而不是 proc 或是 lambda,proc 和 lambda 都是 Proc 对象,它这里的意思就是这里接受一个 Proc 对象,可以用 Proc.new,可以用 proc,可以用 lambda,全都可以。作为教程它当然尽可能的把它所有能接受的参数都告诉你,但并没有推荐你去用 proc 或是 lambda 的意思。

#23 楼 @ericguo 它的例子里确实是用 Proc,可能看起来更加面向对象,不过这些例子用 lambda 也没有问题,还是看编程习惯吧,尽量做到风格统一

又仔细看了看,感觉还是#19 楼 @goinaction 的比较好理解 block 就是一段代码(由 Proc.new 或者 proc 创建,语义完全一样),call 的时候就是当前位置嵌入了该代码块,属于 ruby 比较独特的特性;lambda 相当于一个方法(由 lambda 或者->创建,语义完全一样),虽然还是由一段代码 (block) 组成,但和其他语言的闭包特性很类似,方法体内还可以使用包外的变量,于是 return 到哪里就很容易理解了,return 只是现象,这个才是本质。

Rails Guide 3.2.13 里面使用 Proc,而不是 lambda,也许是因为源码里面是这样起名调用的:

# BlockValidator is a special EachValidator which receives a block on initialization
# and call this block for each attribute being validated. +validates_each+ uses this
# Validator.
class BlockValidator < EachValidator
  def initialize(options, &block)
    @block = block
    super
  end
  def validate_each(record, attribute, value)
    @block.call(record, attribute, value)
  end
end 

https://github.com/rails/rails/commit/279067639f319f3b4bbcaf90c26f286e96df2c77

#27 楼 @ericguo 呵呵,用 Proc 还是 lambda 与起什么名没有关系,你创建一个 BlockValidator 的实例对象试试。贴上你的代码

#26 楼 @ericguo 这个不是本质啊 有意设计成这样的 为了尽可能接近 method 实际实现其实和 proc 是类似的 否则这里 lambda 里的 local variable 也不应该包含 a 才对。。

#29 楼 @iBachue lambada、proc 实现上的本质肯定都是 Proc 对象 我觉得@ericguo 说的本质意思是设计上为啥要区分出 lambda 和 proc,就是为了实现不同效果, 以在不同场合使用。

#28 楼 @kayakjiang 用 Proc 还是 lambda 与起什么名的确没关系 #29 楼 @iBachue 对,顺便略微修改了一下 26 楼。 感谢两位指导,说实话没有这样的讨论估计我是不会去花那么多时间搞 Proc 和 lambda 的特性比较的……

#26 楼 @ericguo #19 楼 @goinaction

我一直的笨办法就是记住:block 就是一段代码,call 的时候就是当前位置嵌入了代码;lambda 相当于一个方法,call 的时候就是方法调用

形象、好记忆。加一句“block 就是一段 绑定了当前作用域变量的代码,call 的时候就在当前位置嵌入了代码”,就更完整了。

def m
  name = "张三"   
  yield              #这里和“张三”没有关系
end

m{p "hi, #{name}"}
#NameError: undefined local variable or method `name' for main:Object
name = "李四"        #当前域生成了name变量
m{p "hi, #{name}"}   #这时,name绑定了   ----(1)
#=>"hi 李四"
name = "王 二"
p1 = lambda{p "hi, #{name}"}    #这时name绑定了, 速度比(1)慢
m(&p1)  
#=>"hi 王二"

m{p "hi, #{name}"} 时,生成了内部 Proc 的对象。

block 不是对象,可以转换成 Proc 对象,对象可以传递。

Proc 的对象 和 Method 的对象 可以相互转换。http://ruby-china.org/topics/10470

不绑定变量,嵌入代码:

def m(mimic) 
  name="zhang 3"
  instance_eval(mimic) 
end 
name = "li 4"
m %{p name}
#=>"zhang 3"

#26 楼 @ericguo #19 楼 @goinaction “嵌入代码”只是便于理解。 block 更接近 method,传入的是 类似 Java 中的 “函数指针”。函数可以有调用参数。

#32 楼例子中的”张三“可以通过块参数进入块中,这时用带参数的块调用。

def m
  name = "张三"     
  yield(name)   #调用传入参数
end
name = "李四"                  #这次和李四没有关系了。
m{|name|p "hi, #{name}"}   #name 是参数
#=>"hi, 张三“

#33 楼 @zputee 呵呵,你这个例子不是很好,%{p name}是一个字符串,类似于

def m(mimic) 
  name="zhang 3"
  eval(mimic) 
end 
name = "li 4"
m "p name"

这等同于

def m
  name = "zhang 3"
  p name
end
name = "li 4"
m

这明摆着会输出 "zhang 3"。

#35 楼 @kayakjiang ,代码块加引号就是字符串 😄 ,来一个真的代码块嵌入:

class C
  def initialize(name)
    @name = name
  end
end
cc = C.new("zhong 3")

@name = "li 4"
cc.instance_eval{
  def hi
    p @name          #调用嵌入对象cc中的实例变量 @name
  end
}
cc.methods(false)   #检查嵌入的代码
#=>[:hi]    
cc.hi              #调用嵌入的代码
#=>"zhong 3"

#36 楼 @zputee 这个例子不错,如果这样改下效果可能更好些,可以排除 def 的干扰

  @name = "li 4"
  cc.instance_eval{
    p @name          #输出zhong 3
  }

 p = Proc.new { @name }

 puts p.call                        #输出的是 li 4

 cc.instance_eval &p           # 输出的是zhong 3,把proc的闭包特性直接无视了


class C
  def initialize
    @name = "zhong 3"
  end

  def hi(p)
     puts p.call
  end

   def hi2(&p)
      puts p.call
   end

end

C.new.hi(p)                 # 此时不能简单的认为proc会嵌入到hi方法中,实际输出的是 li 4
                                 # 如果proc在call的时候能嵌入到hi方法中,那么输出的应该是 zhong 3

C.new.hi2 do
   @name                    # 输出的是li 4
end

instance_eval 是一个比较特殊的方法,感觉它可以无视 proc 的闭包性 在 ruby1.9 中,instance_eval 不能挂 lambda 转换过来的 proc,估计 lambda 的闭包性是强制的,无法无视了哈哈

#37 楼 @kayakjiang 再来个嵌入

class C
  @name = "zhong3"
end

@name="li4"

ll = ->(word){p "#{word},#{@name}"}
C.singleton_class.send(:define_method,:say ,&ll)

C.say("hi")
#"hi,zhong3"

#38 楼 @zputee 呵呵,不玩了,实际开发一般用不了这些东西,代码不能写的让别人看不懂

#38 楼 @zputee 嗯,从参数检查,返回行为,上下文的绑定三个点出发就能掌握 lambda 和 block 的基本使用了

今晚拜读!

好文...挖坟顶上来....

拜读完,感觉这待遇该涨涨了

#45 楼 @luotuo 。。。,请多读读元编程

《Ruby 编程语言》看到第六章完全没看懂。

#48 楼 @kayakjiang 嗯,有道理,非常感谢。

看完这个帖子 又一次明白:我以为我弄明白了一个问题,然而我才刚刚走到门口~~~ 关于 Proc 的 return,也试了一下,很同意 #1 楼 的看法,略微有点坑,以后还是尽量避免这么用好了

51 楼 已删除
chenge Ruby 学习汇集,请推荐内容 提及了此话题。 07月04日 11:35

你真的知道什么是 block 你这样容易误导初学者 好不好 傻 X

哈哈,不是 53 楼,我是不会回这个帖子的。之所以要回,是因为已经不是 2 年前那么小白了。要想知道 block, proc 和 lambda 的区别,没必要想楼主这样,靠似有若无的推测,你只需要看他们三个的 C 语言实现,就能看出他们的区别了,好像是在 ruby.h,intern.h 里吧。也没必要如此喷楼主

EricWJP 回复

做好自己的事情

ane 回复

不同流合污

需要 登录 后方可回复, 如果你还没有账号请 注册新账号