zoukankan      html  css  js  c++  java
  • [转]Ruby之代码块的迷思

    转自:http://ningandjiao.iteye.com/blog/1860099

    块的定义、调用与运行 
    在Ruby中,定义一个代码块的方式有2种 ,一是使用do … end, 另外一种是用大括号“{}”把代码内容括起来。代码块定义时也是可以接受参数的。但是,只有在调用一个方法的时候才可以定义一个块。 

    块定义好之后,会直接传递给调用的方法,在该方法中,使用“yield”关键字即可回调这个块。 

    Ruby代码  收藏代码
    1. def block_method(a, b)  
    2.   a + yield(a, b)  
    3. end  
    4. puts block_method(1, 2) { |a, b| a*2+b } # => 5  
    5. result = block_method(1, 2) do |a, b|  
    6.   a+b*2  
    7. end  
    8. puts result   # => 6  


    如果一个方法定义的时候使用了yield关键字,但是调用的时候却没有传递代码块,方法会抛出“no block given (yield) (LocalJumpError)”异常。可以使用Kernal#block_given?方法检测当前的方法调用是否包含代码块。 

    Ruby代码  收藏代码
    1. def check_block  
    2.   return yield if block_given?  
    3.   'no block'  
    4. end  
    5. puts check_block{ 'This is a block'}  # => This is block  
    6. puts check_block                      # => no block  


    代码在运行的时候,除了需要代码外,还需要运行环境,即一组各种变量的绑定。代码块就是由代码和一组绑定组成的,代码块可以获得自己定义的局部变量的绑定,和上下文可见的实例变量,在代码块执行的时候,只会根据自己定义时可见的绑定来执行。业界把块这样的特性称之为闭包(Closure)。 

    Ruby代码  收藏代码
    1. def closure_method  
    2.   x = "Goodbye"  
    3.   yield("@xianlinbox")  
    4. end  
    5. x = "Hello"  
    6. puts closure_method { |y| "#{x} World, #{y}!" }    # => Hello World, @xianlinbox!  


    作用域 
    前面提到代码运行时,需要一组绑定, 这组绑定在代码的运行过程中,还会发生变化,这种变化发生的根本原因就是作用域发生改变,每个变量绑定都有自己的作用域,一但代码切换作用域,旧的绑定就会被一批新的绑定取代。在Ruby的世界中,作用域相对简单,作用域之间是截然分开的,一旦进入另外一个作用域,原先的绑定就会替换为一组新的绑定,可以通过Kernal#local_variables方法查看当前作用域下的绑定。那么,程序什么时候会从一个作用域跳转到另一个作用域呢? 

    Ruby程序只会在3个地方关闭前一个作用域,同时打开一个新的作用域: 

    • 类定义, class … end
    • 模块定义, module … end
    • 方法定义, def … end


    这三个地方通常称之为作用域们(Scope Gate)。 

    Ruby代码  收藏代码
    1. v1 = 1  
    2. class MyClass  
    3.   v2 = 2  
    4.   puts local_variables.to_s + "call 1"  # => [:v2]call 1  
    5.   def my_method  
    6.     v3 = 3  
    7.     puts local_variables.to_s + " call 2"  
    8.   end  
    9.   puts local_variables.to_s + "call 3"  # => [:v2]call 3  
    10. end  
    11. obj = MyClass.new  
    12. obj.my_method                           # =>[:v3] call 2  
    13. puts local_variables.to_s + "call 4"    # =>[:v1, :obj]call 4  


    因为作用域之间的隔离,让人忍不住会想,怎样才能让一个绑定穿越多个作用域?在Ruby中有一个叫做扁平化作用域(Flat Scope)的概念,通俗点说就是变身拆迁队长,拆掉所有的作用域门。 

    • 用Class.new()方法代替class关键字来定义类
    • 使用Module。new()方法代替module关键字定义模块
    • 使用Module#define_method代替def关键字定义方法。
    Ruby代码  收藏代码
    1. v1 = 1  
    2. MyClass = Class.new do  
    3.   v2 = 2  
    4.   puts local_variables.to_s + "call 1"     # => [:v2, :v1]call 1  
    5.   define_method :my_method do  
    6.     v3 = 3  
    7.     puts local_variables.to_s + " call 2"  
    8.   end  
    9.   puts local_variables.to_s + "call 3"      # => [:v2, :v1]call 3  
    10. end  
    11. MyClass.new.my_method                       # => [:v3, :v2, :v1] call 2  
    12. puts local_variables.to_s + "call 4"        # => [:v1]call 4  


    instance_eval()和instance_exec() 
    在Ruby中,提供了一个非常酷的特性,可以通过使用Objec#instance_eval(), Objec#instance_exec()方法插入一个代码块,做一个的对象上下文探针(Context Proble),深入到对象中的代码片段,对其进行操作。有了这个特性以后,就可以很轻松的测试对象的行为,查看对象的当前状态。 

    Ruby代码  收藏代码
    1. class MyClass  
    2.   def initialize  
    3.     @v = 1;  
    4.   end  
    5. end  
    6. obj = MyClass.new  
    7. obj.instance_eval do  
    8.   puts self              # => #<MyClass:0x007fbb2d0299b0>  
    9.   puts @v                # => 1  
    10. end  
    11. obj.instance_exec(5) { |x| puts x * @v }  # => 5  


    可调用对象 
    从底层来看,对代码块的使用分为2步, 
    * 打包代码备用 
    * 调用yiel执行代码 
    这种先打包,后执行的策略称之为延迟执行(Deferred Evaluation)。 

    代码块在Ruby中并不是一个对象,但是,如果你想把一个代码块保存为一个对象以供调用应该怎么办呢? Ruby提供了Proc类,其简单来说就是一个转换成对象的块。在Ruby中,把一个块转换为Proc对象的方法有以上4种: 

    • * proc{…}
    • * Proc.new { …}
    • * lambda{…}
    • * &操作符
    Ruby代码  收藏代码
    1. proc1 = proc { |x| x+1 }  
    2. puts proc1.class               # => Proc  
    3. proc2 = Proc.new { |x| x+1 }  
    4. puts proc2.class               # => Proc  
    5. proc3 = lambda { |x| x+1 }  
    6. puts proc3.class               # => Proc  


    &操作符只有在方法调用时才有效,在方法定义时,可以给方法添加一个特殊的参数,该参数必须为参数列表中的最后一个,且以&符号开头,其含义就是,这是一个Proc对象,我想把它当做一个块来用,如果调用该函数时,没有传递代码块,那么该参数值将为nil。有了这个符号之后,就可以很容易的把一个代码块转换为Proc对象,并在多个方法之间传递。 

    Ruby代码  收藏代码
    1. def my_method(&block)  
    2.   test(&block)  
    3.   block  
    4. end  
    5. def test  
    6.   puts yield(3) if block_given?  # => 4  
    7. end  
    8. p2 = my_method{ |x| x+1 }  
    9. puts p2.class                     # => Proc  


    Ruby中,所有的可调用对象,最后都是一个Proc对象,为什么还需要使用lambda这个方法来创建一个可调用对象呢? 使用lambda和使用proc创建的Proc对象有一些些微的差别,主要体现在2个方面: 

    • return关键字的行为,lambda中,return仅表示从lambda中返回, 而proc中,则是从定义proc的作用域中返回。
    • 参数校验规则:lambda中,参数个数不对,会抛ArgumentError错误,而proc中,则会尝试调整参数为自己期望的形式,参数多,则忽略多余的,参数少则,自动补nil。
    Ruby代码  收藏代码
      1. def proc_method  
      2.   p = proc { return 10 };  
      3.   result = p.call  
      4.   return result*2  
      5. end  
      6.   
      7. def lambda_method  
      8.   p = lambda { return 10 };  
      9.   result = p.call  
      10.   return result*2  
      11. end  
      12.   
      13. puts proc_method              # => 10  
      14. puts lambda_method            # => 20  
      15.   
      16. p1 = proc { |a, b| [a,b] }  
      17. p2 = lambda { |a, b| [a,b] }  
      18.   
      19. puts p1.call(1, 2, 3).to_s    # => [1, 2]  
      20. puts p1.call(1).to_s          # => [1, nil]  
      21. puts p2.call(1, 2, 3).to_s    # => ArgumentError  
      22. puts p2.call(1).to_s          # => ArgumentError  
  • 相关阅读:
    要成功先发疯
    情绪ABC理论
    树立和提高威信法
    javaagent
    sonar 使用
    sonar 代码质量管理
    四大思维工具,SWOT、PDCA、DISC、时间管理
    HyperLogLog
    位数组
    git checkout .和git checkout -f的区别;git add . git add -u git add -A的区别
  • 原文地址:https://www.cnblogs.com/linganxiong/p/6109991.html
Copyright © 2011-2022 走看看