2010-07-01

ruby中的代码块(Code Blokcs)

ruby中的代码块对新手来说有点晕。其实这并不是一个什么新创的东西,在其他语言中你是碰到过的。例如C语言中的函数指针,C++的函数对 象,Python的lambda表达式和列表解析,Perls的匿名函数,Java的匿名内部类。如此看来ruby的代码块只不过是新瓶装旧酒罢了。 1、ruby提供了两种block的写法{}或者 do ... end。而且前者的运算优先级高于后者。
irb(main):144:0> 1.upto 3 {|x| puts x}
SyntaxError: compile error
(irb):144: syntax error, unexpected '{', expecting $end
1.upto 3 {|x| puts x}
^
from (irb):144
from /usr/lib/ruby/1.8/fileutils.rb:875
irb(main):145:0> 1.upto(3) {|x| puts x}
1
2
3
=> 1
irb(main):146:0> 1.upto 3 do |x|
irb(main):147:1*   puts x
irb(main):148:1> end
1
2
3
=> 1
通过上面可以看出ruby的block就是一个匿名函数,“|  | ” 之间的参数相当与函数的参数。同时这里的参数又具有严格的限制:
  • block参数不能有默认值
  • block的参数不能是block对象
2、由于Proc对象可以像其他对象一样被操作,所以可以构建block工厂
irb(main):001:0> def n_times(n)
irb(main):002:1>   lambda {|x| x*n}
irb(main):003:1> end
=> nil
irb(main):004:0> ten_times = n_times(10)
=> #<Proc:0x00007f3b30e51330@(irb):2>
irb(main):005:0> ten_times.call(1)
=> 10
irb(main):006:0> ten_times.call(1.25)
=> 12.5
####### 计算周长  #####################
irb(main):007:0> circumference = n_times(2*Math::PI)
=> #<Proc:0x00007f3b30e51330@(irb):2>
irb(main):008:0> circumference.call(10)
=> 62.8318530717959
irb(main):009:0> [1,2,3].collect(&circumference)
=> [6.28318530717959, 12.5663706143592, 18.8495559215388]
或许你之前听说过ruby的闭包(closures)。那么什么是闭包呢,它与block的区别是什么呢?其实在ruby中,closures和blocks没有区别。每一个ruby 的 block 就是一个 closure 。 3、创建一个block以及block的调用
  • 用Proc的构造器构造block
    irb(main):016:0> say = Proc.new {"Hello world"}
    => #<Proc:0x00007f3b30dde790@(irb):16>
    irb(main):017:0> say.call
    => "Hello world"
    irb(main):018:0> log = Proc.new {|str| puts "[LOG] #{str}" }
    => #<Proc:0x00007f3b30dd0028@(irb):18>
    irb(main):019:0> log.call("A test log message.")
    [LOG] A test log message.
    => nil
    
  • 用Kernel#proc构造block
    irb(main):020:0> say = proc {"Hello world"}
    => #<Proc:0x00007f3b30e8bda0@(irb):20>
    irb(main):021:0> say.call
    => "Hello world"
    irb(main):022:0> log = proc {|str| puts "[LOG] #{str}" }
    => #<Proc:0x00007f3b30e59cd8@(irb):22>
    irb(main):023:0> log.call("A test log message.")
    [LOG] A test log message.
    => nil
  • 用Kernel#lambda构造block
    irb(main):155:0> say = lambda {"Hello world"}
    => #
    irb(main):156:0> say.call
    => "Hello world"
    irb(main):157:0> log = lambda {|str| puts "[LOG] #{str}" }
    => #
    irb(main):158:0> log.call("A test log message.")
    [LOG] A test log message.
    => nil
    
它们之间的区别:
  • lambda:
    irb(main):001:0> add_lambda = lambda {|x, y| x + y}
    => #<Proc:0x00007f43af642630@(irb):1>
    irb(main):003:0> add_lambda.call(1)
    ArgumentError: wrong number of arguments (1 for 2)
    from (irb):1
    from (irb):3:in `call'
    from (irb):3
    from :0
    irb(main):004:0> add_lambda.call(1,2,3)
    ArgumentError: wrong number of arguments (3 for 2)
    from (irb):1
    from (irb):4:in `call'
    from (irb):4
    from :0
    
  • Proc.new:
    irb(main):005:0> add_proc_new = Proc.new {|x, y| x + y}
    => #<Proc:0x00007f43af6028a0@(irb):5>
    irb(main):006:0> add_proc_new.call(1)
    TypeError: nil can't be coerced into Fixnum
    from (irb):5:in `+'
    from (irb):5
    from (irb):6:in `call'
    from (irb):6
    from :0
    irb(main):007:0> add_proc_new.call(1,2,3)
    => 3
    
    注意:当实参少于形参时,缺少的参数默认值为nil;当实参多于形参时,多于的参数会被忽略。
  • proc 在ruby 1.8 中,Kernel#proc与Kernel#lambda一致。在ruby 1.9中,Kernel#proc与Proc.new一致。
4、借助&符号创建自定义lambda表达式
irb(main):010:0> def my_lambda(&aBlock)
irb(main):011:1>   aBlock
irb(main):012:1> end
=> nil
irb(main):013:0> b = my_lambda {puts "Hi, nice to meet you!"}
=> #<Proc:0x00007f3b30defe28@(irb):13>
irb(main):014:0> b.call
Hi, nice to meet you!
=> nil
irb(main):015:0> b.class
=> Proc
5、自定义可以接收块的方法 在ruby中,任何方法都可以接受块,只要方法的调用者传入的有block。在方法体中,通过yield调用blcok。
irb(main):019:0> def repeat(n)
irb(main):020:1>   if block_given?
irb(main):021:2>     n.times {yield}
irb(main):022:2>   else
irb(main):023:2*     raise ArgumentError.new("I can't repeat a block you don't give me!")
irb(main):024:2>   end
irb(main):025:1> end
=> nil
irb(main):026:0> repeat(3) { puts "Hello world!"}
Hello world!
Hello world!
Hello world!
=> 3
irb(main):027:0> repeat(3)
ArgumentError: I can't repeat a block you don't give me!
from (irb):23:in `repeat'
from (irb):27
from :0
yield 是可以传递参数的
irb(main):029:0> squares = {0=>0,1=>1,2=>4,3=>9}
=> {0=>0, 1=>1, 2=>4, 3=>9}
irb(main):030:0> squares.find {|key,value| key > 1}
=> [2, 4]                      # Hash#find当找到满足条件的就停止,以数组的形式返回满足条件的键值对
irb(main):031:0> class Hash
irb(main):032:1>   def find_all
irb(main):033:2>     new_hash = Hash.new
irb(main):034:2>     each {|k,v| new_hash[k] = v if yield(k,v)}
irb(main):035:2>     new_hash
irb(main):036:2>   end
irb(main):037:1> end
=> nil
irb(main):038:0> squares.find_all {|key,value| key > 1}
=> {2=>4, 3=>9}

irb(main):053:0> def invoke_on_each(*args, &block)
irb(main):054:1>   args.each {|arg| yield arg}
irb(main):055:1> end
=> nil
irb(main):056:0> invoke_on_each(1,2,3,4) {|x| puts x ** 2}
1
4
9
16
=> [1, 2, 3, 4]
blog comments powered by Disqus