Proc对象

来源:互联网 发布:工作日志管理系统php 编辑:程序博客网 时间:2024/06/09 23:32

对象的原生行为

  • 查看原生行为

    irb> puts Object.new.methods.sortirb> [:!, :!=, :!~, :<=>, :==, :===, :=~, :__id__, :__send__, :class, :clone, :define_singleton_method, :display, :dup, :enum_for, :eql?, :equal?, :extend,:freeze, :frozen?, :hash, :inspect, :instance_eval, :instance_exec, :instance_of?, :instance_variable_defined?, :instance_variable_get, :instance_variable_set, :instance_variables, :is_a?, :itself, :kind_o, :method, :methods, :nil?, :object_id, :private_methods, :protected_methods,:public_method, :public_methods, :public_send, :remove_instance_variable, :respond_to?,:send, :singleton_class, :singleton_method, :singleton_methods, :taint, :tainted?,:tap, :to_enum, :to_s, :trust, :untaint, :untrust, :untrusted?]
  • 用 object_id 方法唯一标识对象

    irb>obj = Object.newirb>obj.object_idirb>70206757165840irb>str = "abc"irb>70206756020620
  • 用 respond_to?方法查询对象的能力

    irb>obj = Object.newirb>if obj.respond_to?("talk")irb>    obj.talkirb>elseirb>    puts "sorry,the object does's understand the 'talk' message"irb>endirb>sorry,the object does's understand the 'talk' message
  • 用send方法发送信息给对象

Proc对象

  • 使用 Proc.new 创建 Proc 的实例。通过实例化 Proc 类并包含代码块,创建 Proc 对象:

    irb> pr = Proc.new{puts "Insid a Proc's block"}irb> pr.call

    proc 方法:

    proc 方法携带一个代码块然后返回一个 Proc 对象。因此可以说 proc { puts “Hi!” }代替 了 Proc.new { puts “Hi!” },它们有相同的结果。Proc.new 和 proc 通常稍有不同,proc 作为匿名的方式用于 lambda(详见 14.2 节),而 proc/lambda 方法则会产生特有的 Proc 对象, 它与 Proc.new 产生的对象不完全相同。这里是有些让人困惑,所以现在尽管 Proc 对象有这两种变 化形式,但暂且把 Proc.new 和 proc 看作同一事物,相反 lambda 则会产生其他的变化。至少,从 命名上它们更为相似。

  • proc 和代码块以及区别

    创建 Proc 对象时,总是要提供一个代码块。但不是每个代码块都可以作为 proc 的主要成 分。这个片段:

    irb> [1,2,3].each{|x| puts x * 10}

    涉及一个代码块但是并没有创建 proc。亊情要比之前的复杂一些。使用第 9 章中短暂所见的特殊 参数语法,方法会捕获一个代码块,将其对象化成为 proc。

    irb> def call_a_proc(&block)        block.call    endirb> call_a_proc{puts "I'm the block ... or Proc ...or something"}irb> I'm the block ... or Proc ...or something

    但是通过使用相似的特殊语法,proc 能够代替在方法调用时的代码块:

    ibr> p = Proc.new{|x| puts x.upcase}    %w{Divid Black}.each(&p)irb> DIVID     BLACK
  • 代码块与proc相互转换:

    代码块和 proc 相互转换非常容易——不用惊讶,因为代码块存在的目的就是被执行,而 proc 是对象,它的任务就是执行之前定义好的代码块。首先将看到代码块转换为 proc,稍后会看到 proc 替代代码块的使用。

    获取代码块作为proc

    下面将以另一个简单的示例方法作为开端,该方法会捕获自己的代码块作为Proc 对象然后调用这个对象:

    irb> def capture_block(&block)        block.call     endirb> capture_block{puts "Inside the block"}

    注:&标记也会出现在执行另一个转换操作时:使用 Proc 对象代替代码块时
    &block = Proc.new{puts "Inside the block"}
    capture_block{puts "Inside the block"}

  • 对代码块使用proc

    下面是如何使用 proc 代替代码块时,调用 capture_block 的做法:

    irb>p = Proc.new{puts "This proc argument will serve as a code block"}irb>capture_block(&p)irb>This proc argument will serve as a code block
  • to_proc方法概述

    **理论上,可以在任何类中或任何对象中定义 to_proc 方法,然后这些受影响的对象就可以 运用&标记的技术了。可能不需要为这个做太多的工作,对于 to_proc 最有用的两个类是 Proc (乊前谈论到)和 Symbol(下一节中讨论),且 to_proc 行为已经被内建在这些类中。不过看 一下 to_proc 是如何与自定义的类合并使用的,可以对编程语言表面乊下的强大动态特性有一
    些感觉。**

    下面是一个相当奇怪但具有指导性的代码:

     irb> class Person        attr_accessor :name        def self.to_proc            Porc.new{|preson| person.name}        end     end irb>d = Person.new  irb>d.name = "mafeihu" irb>m = Preson.new irb>m.name = "Matz" irb>puts [d,m].map(&Person) irb>mafiehu      Majosn
  • 简洁的Symbol#to_proc

    内置方法Symbol#to_proc在如下情况中使用

    ibr>%{ david black}.map{&:capitalize}irb>["David","Blocak"]

    注:
    符号:capitalize 被解释为依次収送到数组中每个元素的消息。因此,前面的代码与下面 的对等:

    irb>%w{david black}.map.{|str| str.captitalize}

    但是,正如代码展示的,它更为简洁。
    如果看到了&:capitalize 或者在代码中看到过相似的构造,可能会认为这很神秘。但是 了解它的解析原理(即了解:capitalize 是一个符号,还有&是一个 to_proc 触収器)则会正 确地理解它,并欣赏它的表现力。

    Symbol#to_proc 还可以很好地用于不使用圆括号的情况:

    irb>%w{david black}.map &:capitalize

    将圆括号去掉后,可以让这个被 proc 加工过的符号看起来很像是位于代码块所在的位置。 当然,这里没有必要这样做。应该记住的是:每当使用 to_proc &标识符时,就是在通过&把 proc 标记为参数迚行传递,而不用再提供代码块。
    与 Ruby 的其他亊物相比,Ruby 所提供的 Symbol#to_proc 是一个很好的例子,可以让用 户能够在必要时简化所编写的代码。

    实现Symbol#to_proc

    irb>%{david black}.map(&:capitalize)    等同于irb>%w{david black}.map{|str| str.capitalize}    等同于irb>%w{david black}.map{|str| str.seed(:capitalize)

    通常来说,不用为它编写什么,因为如果能够使用常觃的点运算符语法调用方法,就不必费力 去使用 send。但是基于 send 编写的版本可以指明 Symbol#to_proc 的实现方式。在本例中代码 块的任务是为了収送符号:capitalize 给数组的每个元素。这就意味着通过:capitalize #to_proc 创建的 Proc 一定会将:capitalize 作为参数収送给它自己。将这些概念总结一下,可调用和可运行对象以提出这个对 Symbol#to_proc 的简单(人们会说这几乎有点虎头蛇尾)实现:(理解有问题)

    class Symbol    def to_proc        Proc.new{|obj| obj.send(self)}    endend
  • proc作为闭包使用

    irb>def call_some_proc(pr)        a = "irrelevant 'a' in menthod scope"        puts a        pr.callirb>endirb>a = "'a' to be used in Proc block"irb>pr = Proc.new{puts a}irb>pr = callirb>call_some_proc(pr)irb>irrelevant 'a' in menthod scopeirb>'a' to be used in Proc block

    经典的闭包示例是计数器。下面的方法返回了一个闭包(绑定了局部变量的一个 proc)。proc被用作计数器,它会在每次调用的时候增加其变量的值:

    irb>def make_counter        n = 0        return Proc.new{ n +=1 }irb>endirb>c = make_counterirb>puts c.call         irb>puts c.callirb>d = make_counterirb>puts d.callirb>puts c.call输出结果如下:1213

    proc 中的逻辑是 n 加 1��,因此 proc 第一个被调用时,它得到的值为 1,第事次是 2,以此类推。 调用 make_counter 和稍后调用 proc 的返回值确认了这一点:第一次 1 被打��出来,第事次是 2��。 但是从 1 开始,新计数器再次创建,第事次调用 make_counter 创建了一个��,而局部变量 n 被 保存在不同的 proc 中。前两个计数器的不同点,通过第三次对第一个计数器的调用可以解释清楚, 这一次打��输出 3��。在 proc 创建的时候,其内部会使用被保存的变量 n,这个过程会不断地延续。
    如同所有的代码块,创建 Proc 对象时提供的代码块可以带有一个参数。下面会深入讲解在 Proc 创建过程中,代码块的实际参数和形式参数是如何工作的。

  • Proc的形式参数和实际参数

    下面是proc的实际化过程,并携带有一个参数的代码块:

    irb>pr =Proc.new{|x|puts "Called with arguemnt#{x}"}ibr>pr.call(100)结果输出为:called with arguement 1000

    proc 与方法的不同在于参数的处理方式,因为它们并不关心参数的数量是否正确。只有一个参数的 proc 是如下形式:

    >>pr = Proc.new{|x| p x}=>#<Proc:0x007fa07481e9a0@(irb):28>

    **它被调用时可以携带任意数量的参数,甚至没有参数。如果调用的时候没有参数,它的单一
    参数将被设置为 nil:**

    >>pr.callnil

    如果调用时多于一个参数,那么单一的参数将会绑定到第一个参数,其他参数将被丢弃:

    >>pr.call1
  • 使用lambda和->创建函数

    如同 Proc.new,使用提供的代码块作为函数主体,lambda 方法也会返回一个 Proc 对象:

    >>lam = lambda{puts "A lambda!"}=> #<Proc:0x007fa07482d040@(irb):31 (lambda)>>>lam.callA lambda!

    从对字符串的检查中可以看出,从 lambda 中返回的是 Proc 类的对象。但是要注意 (lambda)表示法,虽然没有 lambda 类存在,但是 Proc 类有一个独特的 lambda 风栺。 lambda 风味的 proc 与��通 proc 有 3 个地方稍有不同:

    首先,lambda 需要明确的创建过程。不论 Ruby 在何处隐式地创建 Proc 对象,它们都是常 觃的 proc 而不是 lambda。这首先就意味着当抓取一个在方法中的代码块时,如:

    def m(&block)

    抓取到的Proc对象是一个常规proc而不是lambda

    **其次,lambda 与 proc 的不同是它们对待 return 关键字的方式。在 lambda 中的 return
    会触収整个 lambda 主体立即退出 lambda 所在的代码上下文。proc 中的 return 只会从 proc 被 执行所在的方法中返回。下面有对这个不同点的说明:**

    def return_test    l = lambda{ return }    l.call    puts "still here"    p = Proc.new{return}    p.call    puts "You won't see this message"endreturn_test

    这个代码片段的输出为”Still here!”。读者不会看到打��第事条消息��,因为对 Proc 对象��的调用触収了从 return_test 方法的返回。但是对 lambda 的调用��则会触収整个 lambda 主体的返回(退出),然后方法会从上一次中断的地方继续执行。

    警告:因为在proc(非lambda风味)内部的return触发了闭合方法的返回,
    当没有位于方法内部调用包含 return 的 proc 时,会产生一个严重错误。
    如果要看到这个错误的演示过程,可试着在命 令行执行如下命令:ruby -e 'Proc.new
    { return }.call'。

    **最后,最为重要的是,lambda 风栺的 proc 不能在调用的时候使用错误的参数数目。它们是
    非常挑剔的:**

    >>lam = lambda {|x| p x}=> #<Proc:0x007fa07309da60@(irb):33 (lambda)>>>lam.call(1)1=>1>>lam.callArgumentError: wrong number of arguments (given 0, expected 1)>>ca,.call(1,2,3)ArgumentError: wrong number of arguments (given 3, expected 1)除了lambda方法,还有一个lambd的字面构造器。
  • lambda构造器->

    lambda构造器(昵称为 “sabby lambda”)是这样运用的:

    >>lam = ->{puts "hi"}=>#<Proc:0x007fa073a06250@(irb):38 (lambda)>>>lam.callhi

    如果想要让 lambda 带有参数,就需要将参数放在->乊前的圆括号中,与代码块中把参数放 置在两条竖线中间不同:

    >>mult = ->(x,y){x * y}=> #<Proc:0x007fa07295d7c8@(irb):39 (lambda)>>>mult.call(3,4)=>12
0 0