美文网首页
block, proc, lambda

block, proc, lambda

作者: AQ王浩 | 来源:发表于2016-01-02 11:15 被阅读115次

    &:to_i是一个block,block不能独立存在,同时你也没有办法直接存储或传递它,
    必须把block挂在某个方法后面。

    一、从最简单的看起

    
      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" })
      f3(->{ puts "f3" })
    
    

    由于 &p 并不作为方法的参数,所以f2
    不能传递一个参数,f2需要的是一个block。

    &p 相等于一种申明,当方法后面有block的时候,会把block捕捉进来。

    f3 需要一个Proc的参数,所以就需要传递一个Proc进去。

    二、block

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

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

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

    
    >> def f(&p)
    >>   end
    => :f
    >> f(1)
    ArgumentError: wrong number of arguments (1 for 0)
        from (irb):72:in `f'
        from (irb):74
        from /Users/wanghao/.rvm/rubies/ruby-2.2.2/bin/irb:11:in `<main>'
    >> f
    => nil
    >> f(){ puts "f" }
    => nil
    >> f()
    => nil
    
    

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

    
    >> def f(a)
    >>   puts a
    >>   end
    => :f
    >> f(1) { puts 2 }
    1
    => nil
    
    

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

    
    >> def f1
    >>   yield
    >>   end
    => :f1
    >> def f2(&p)
    >>   p.call
    >>   end
    => :f2
    >> f1
    LocalJumpError: no block given (yield)
        from (irb):88:in `f1'
        from (irb):93
        from /Users/wanghao/.rvm/rubies/ruby-2.2.2/bin/irb:11:in `<main>'
    >> f2
    NoMethodError: undefined method `call' for nil:NilClass
        from (irb):91:in `f2'
        from (irb):94
        from /Users/wanghao/.rvm/rubies/ruby-2.2.2/bin/irb:11:in `<main>'
    >>
    
    

    为了保证不抛出异常,我们可以这么修改

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

    这样,f1和f2后面无论挂不挂block都不会抛异常了。

    
    def f2(&p)
      puts p.class
      puts p.inspect
      p.call
    end
    
    >> f2{->{ puts "123" }}
    Proc
    #<Proc:0x007fb57c0884d0@(irb):102>
    => #<Proc:0x007fb57c088390@(irb):102 (lambda)>
    
    

    ["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
    => :to_proc
    >> add_by_9 = AddBy.new(9)
    => #<AddBy:0x007fba3997c2c8 @num=9>
    >> puts [1,2,3].map(&add_by_9)
    10
    11
    12
    => nil
    
    
    

    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
    
    
    

    我们经常在该挂block的时候,却把proc对象当参数传给方法了,
    或者不明白&p就是block可以直接交给方法使用

    ** &p是block, p是proc 不到万不得已的情况下不要显式地创建proc **

    三、lambda

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

    
      >> p = Proc.new {}
      => #<Proc:0x007fba3a8dd708@(irb):11>
      >> puts p
      #<Proc:0x007fba3a8dd708@(irb):11>
      => nil
      >> l = lambda {}
      => #<Proc:0x007fba3a8cd498@(irb):13 (lambda)>
      >> puts l
      #<Proc:0x007fba3a8cd498@(irb):13 (lambda)>
    
    

    从这里可以理解ruby的设计者们确实在有意的区分
    lambda和proc,并不想把lambda和proc混在一起,如同ruby中
    没有叫Block的类,除非你自己定义一个,ruby中也没有叫
    Lambda的类,于是将lambda对象化的活儿就交给了Proc。

    当你用lambda弄出一个匿名方法时,发现它是一个proc对象,并且
    这个匿名方法能干的活,proc对象都能做,于是我们不淡定了。

    
      Proc.new{} 这样可以
      proc {} 这样也没有问题
      lambda {} 这样做也不错
      -> {} 这个还是能行
    
    

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

    
    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
    
    
    

    验证proc不需要参数校验,而lambda需要参数校验。

    
    >> def f5()
    >>   yield 1,2
    >>   end
    => :f5
    >> p1 = proc { |i,j| puts j }
    => #<Proc:0x007fba3a856370@(irb):42>
    >> p2 = proc { |i| puts i}
    => #<Proc:0x007fba398f0c28@(irb):43>
    >> l1 = lambda { |i,j| puts j }
    => #<Proc:0x007fba398e0580@(irb):44 (lambda)>
    >> l2 = lambda { |i| puts i }
    => #<Proc:0x007fba3a845160@(irb):45 (lambda)>
    >> f5(&p1)
    2
    => nil
    >> f5(&p2)
    1
    => nil
    >> f5(&l1)
    2
    => nil
    >> f5(&l2)
    ArgumentError: wrong number of arguments (2 for 1)
        from (irb):45:in `block in irb_binding'
        from (irb):40:in `f5'
        from (irb):49
        from /Users/wanghao/.rvm/rubies/ruby-2.2.2/bin/irb:11:in `<main>'
    >>
    
    

    四. 总结

    1. block和Proc都是代码块,Proc可以复用,block不可以复用

    2. block和lambda不是类,Proc是类,所以block和lambda是小写

    3. lambda 是匿名函数

    4. lambda对参数个数验证,Proc不会验证

    5. lambda会执行return,Proc遇到return会中断

    6. lambda不会转换成block,Proc可以转换成block

    方法定义中&p参数,相等于声明,如果后面包括block的话,就会将后面的block捕捉进来放在p中

    任何方法都可以挂载block,如果想要block做一些事情,就需要在定义方法的时候,指定yield或&p

    1. ["1", "2", "3"].map(&:to_i) 相等于 ["1", "2", "3"].map{ |obj|
      obj.to_i }
      这里的魔法在于符号&会触发:to_i的to_proc方法,to_proc方法执行后
      会返回一个proc实例,然后&会把这个proc转换成block。并且map后面跟
      的也是一个block

    2. &p中,&p是block,而p是proc

    ruby-china

    相关文章

      网友评论

          本文标题:block, proc, lambda

          本文链接:https://www.haomeiwen.com/subject/twuwhttx.html