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

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

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

  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。

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