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

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

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

  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

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

大家接着补充.



共收到 22 条回复

一时想不到,本来差别在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。

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