Ruby 发起一个讨论 有关 block 的用途.

zw963 · 2012年03月04日 · 最后由 skandhas 回复于 2012年03月05日 · 4545 次阅读

我自己先说几个最常见的。

  1. 作为迭代器。貌似不用示例了。来这的人都知道。

  2. 传入逻辑,类似于`设计模式'中经典的委托模式. 示例:

    # 将 y <=> x (任意两元素)的<=>方法比较结果作为`排序的逻辑', 传入sort方法.
    [1, 3, 38, 24, 4, 5].sort {|x, y| y <=> x }             # => [38, 24, 6, 4, 3, 1]
    
  3. 使用 lambda, 形成一个闭包,这个我经验比较少,目前还没有什么地方用到。 示例:

    def return_a_closure
    x = "zw963 say: "
    lambda {puts x + yield}
    end
    

在方法定义之外,总是可局部变量 x

a_closure = return_a_closure {"hello world!"} # => "zw963 say: hello world!" a_closure.call another_closure = return_a_closure {"hello ruby!"} # => "zw963 say: hello ruby!" a_closure.call

谁可以告诉我, 具体在那些的实现中, 用到了闭包?

大家接着补充.



一时想不到,本来差别在 return 上,不过 ruby 函数最后一句就是返回值这个特性似乎让 lambda 的使用场合少了很多

像 ActiveRecord 里写 scope 时,如果和时间相关,就得放到 lambda 里每次重新去算。

以前写过一个 space_less 的 Helper 方法,用于去除空白字符。

<% space_less do %>
...
<% end %>

在对象的 initalize 中yield self if block_given? 之后

my_object = MyObject.new do |o|
  o.foo = "bar"
  o.bar = "foo"
end

我想这个是很常用的

写 dsl 的时候可以用到,比如:

class A
  def self.help(&block)
    a = A.new 
    a.instance_exec &block
  end

  def method1
    puts "method1"
  end

  def method2
    puts "method2"
  end

end

A.help do 
 method1 
 method2
end

写 helper 来包页面内容时还挺常用的,配合 capture

  1. block 在实作 DSL 时,是很有用处的。如 @allenwei 说的 :>
  2. lambda 的例子,很多程序或是 gem 内部都经常使用 lambda,你在看源代码时应能碰到。

比如你写一个极简单的 Rack App:

app = Rack::Builder.app do
   use Rack::CommonLogger
   run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['OK']] }
end

Rack::Response 内部也使用了 lambda。等等,例子很多。 :> lambda 一个很重要的作用是可以把调用推迟。以前用 Ruby 写过一个 ARM 指令解释器,当时的实作是用 lambda 来对应指令。解释时,遇到指令就执行相应的 lambda。

我来总结下: #5 楼的用途,和我总结的传入一个策略,基本上相似。

#3 楼 #6 楼, 跟 attr_accessor 的用法差不多。虽然不完全相同。 前者上下文空间通过类方法被定义,后者在 Module 类中被定义而已, @allenwei , 的这种定义方式的确在 Ruby 极多,rake, rails 都大量用到. 他们非常相似的地方是:它们都使用 block 创建了一个包含上下文空间的块. (在块中更改了 self 值,就像类定义一样,可以使用一些特殊的方法)

#8 楼, @skandhas, 你说的延后调用,是不是和 RSpec 中的下面代码类似。

...
lambda { do something }.run    # 这个run是在那里定义的? 为什么不用call
...

我感觉就像 bash 里面的$(...) 一样。

@allenwei , 我问一下,你的那个 DSL 示例,写成下面这样,是否会存在问题?

class A
  def self.help(&block)
    self.instance_exec &block
  end

  def self.method1
    puts "method1"
  end

  def self.method2
    puts "method2"
  end

end

A.help do
  method1
  method2
end

我总觉得先创建了 A 的实例对象,再调用实例方法,是否有些多余?

#2 楼 @lainuo 我的印象里,只要是 scope 依赖动态的参数,就需要 lambda,特别是依赖输入参数 scope :aged_0, lambda{ where("created_at IS NULL OR created_at < ?", Date.today + 30.days).joins(:owner) } scope :aged_1, lambda{ |d_time|where("created_at IS NULL OR created_at < ?", d_time).joins(:owner) }

#10 楼 @zw963 效果一样,感官可大不同 比如:

class 
  def self.来一个(&block)
    self.instance_exec &block
  end

  def self.扫地
    puts "扫完了"
  end

  def self.做饭
    puts "做好了"
  end

end

.来一个 do
  扫地
  做饭
end

这样可以,但你说是不是没有前面那么写来得好?这是艺术问题,不是技术问题。

@ken_lv 恩,是这样的,因为如果不是,那在 load 的时候就定死了

#9 楼 @zw963 关于楼主 block 的用途,我多少有一点体会 block 本质是匿名方法,和 hash loop class recursion 一样就是一个编程的工具,没有的话程序都可以写,就是特别的场合,有特别适用的地方。 那么,block 匿名方法什么时候适用呢?方便理解也举个例子吧, 有两组数据,可能要进行不同处理

def cross_loop(array_a, array_b, process)
  array_a.each do |element_a|
    array_b.each do |element_b|
      process.call(element_a, element_b)
    end
  end
end

cross_loop([1,2,3], [3,4,5], lambda{|x,y| puts x + y})
cross_loop([1,2,3], [3,4,5], lambda{|x,y| puts x * y})

说不用 lambda 行不行,当然,最基本可以在 cross_loop 里面调用一个函数。只不过重用的时候不好,因为没有 process 的参数。 所以,最基本的需求场合是,定义方法的时候,有些操作当时不能确定,需要调用的时候,动态确定。 当然,也可以不用 lambda 又动态传入 process,写个 send 然后,再写些 process 函数也可以。

def cross_loop(array_a, array_b, process)
  array_a.each do |element_a|
    array_b.each do |element_b|
      self.send(process, element_a, element_b)
    end
  end
end
def plus(a, b); puts a + b; end
def multiply(a, b); puts a * b; end
cross_loop([1,2,3], [3,4,5], 'plus')
cross_loop([1,2,3], [3,4,5], 'multiply')

只是跟 block 比,各有所长吧

#10 楼 @zw963 如果你有些 instance_variable 就不太合适了,所以为了隔离一般都会创建新的实例

@ken_lv ,嗨~ 我完全明白你的意思。虽然你的代码有多处错误。呵呵。

不过我觉得你说的那个场合,并不是特别需要 lambda 的场合,相反, 你说的这个需求,正是代码块参数最擅长的地方. (这里我把代码块当作一个普通方法的一个特殊参数来看待,这样理解很好)

显然,你的代码更好的实现是:

def cross_loop(array_a, array_b, &process)
  array_a.each do |element_a|
    array_b.each do |element_b|
      yield array_a, array_b
    end
  end
end

cross_loop([1,2,3], [3,4,5]) {|x, y| puts x + y}
cross_loop([1,2,3], [3,4,5]) {|x,y| puts x * y}

很显然,lambda 真正特殊的地方是它的闭包特性,还有就是#8 楼说的,延后执 行,这两个特性,你的代码都没有涉及。

@clc3123 , 我明白了。

如果以上代码,是在 initialize 中实现,这种方式显得更自然。

A.new do
  ...
  ...
end

#14 楼 @ken_lv block 的本质不是匿名方法,因为 block 的 return 会把调用的方法给返回掉,lambda 本质才是匿名方法。

又想起一个 lambda 的实例,是 Programming ruby 1.9 里面的好像。

一个对象 o 在某个安全级别下运行,其内部机制实际上为: 通过一个 Proc 对象,将对象 o 裹 (wrap) 起来,并在代码块内设定需要的安全级别。 例如:

lambda {$SAFE = 3; ... }.call

我不知道该怎么描述这种用法,也许 8 楼所说的延后执行,只是神秘的 lambda 其 众多含义中的一种而已。

puts_hello = lambda{ puts "hello world"}.call
puts_hello      # => 输出 "hello world"

其实以上这种简单的实现,使用 proc 又未尝不可呢? 只不过在某些复杂点的场合,使用 lambda 出错的几率小一点。

puts_hello = proc {puts "hello world" }.call
puts_hello      # => 输出 "hello world"

我觉得说了这么一堆示例,其核心内容,还是以下两点:

  1. lambda 更像一个方法 (函数) 调用。(其实就是一个匿名方法,而 proc 只是一个代码块参数)
  2. lambda 是闭包的。

在某些场合,以上两点可能很重要。只是我资历尚浅,闭包没见过实际应用。呵呵。

#19 楼 @zw963 rack 的 use 方法非常形象的例子。

#16 楼 @zw963 @soloara 惭愧,见笑啦。不过也挺好,纠正了我的一些似是而非,模糊不清的认识。

#19 楼 @zw963 proc 和 lambda 虽有区别,但是有一点很重要:,它们都是 Proc 类的实例。 你可以:

p lambda{ puts "hello world"}.class

另 Proc#lambda? 可以判断这个 Proc 实例是否是 lambda。

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