首页 > 代码库 > 201706 Ruby 基础 & 元编程

201706 Ruby 基础 & 元编程

yield

所有的"方法(methods)"隐式跟上一个"块(block)"参数。

块参数也可以明确给定,形式就是在参数前面加一个"&",比如 def fn(arg1, arg2, &block) end,其中的 &block 就是明确给定的块参数。

块参数的动作,可以通过调用 call() 方法执行,还可以用 yield 来执行 —— yield 其实就是一个语法糖。

所以以下几种写法常常是等价的:

#method receives an invisible block argument
def foo1()
    yield 1
end

#specify it explicitly
def foo2(&block)
    yield 1
end

#yield is equal to block.call
def foo3(&block)
    block.call(1)    
end

#function call
foo1 {|x| puts x}    # => 1
foo2 {|x| puts x}    # => 1
foo3 {|x| puts x}    # => 1

注意事项
method 定义中 &block 参数必须在最后

yield self

在一个对象中,self 表示是一个当前对象的引用。

所以,常见的 yield self if block_given? 中的 self 就和其它地方使用 self 一样,没什么特殊的。

Proc

前面说到所有方法都可以隐式或显式指定一个块参数,那么块参数到底是什么呢?

答案是 Proc 对象,一个具有 call 方法的对象。

Proc 对象的定义有几种形式:

  • 直接使用 {}
  • 使用 Proc.new {}
  • 使用 proc {}
  • 使用 lambda {}
#yield is equal to block.call
def foo(&block)
    puts block.class
    puts block.to_s
    yield 1    
end

#function call
# Proc created using {} syntax
foo {|x| puts x}   
# => Proc
# => #<Proc:0x00000000e0b140@(ruby):9>
# => 1

# Proc created with the "proc" keyword. Note & syntax when calling.
my_proc = proc { |n| puts n }
foo(&my_proc)
# => Proc
# => #<Proc:0x00000000e0b140@(ruby):12>
# => 1

# Proc creates with Proc.new
my_proc = Proc.new { |n| puts n }
foo(&my_proc)    # => 1
# => Proc
# => #<Proc:0x00000000e0b140@(ruby):16>
# => 1

# Proc created with the "lambda" keyword. Nearly same thing.
my_proc = lambda { |n| puts n }
foo(&my_proc)
# => Proc
# => #<Proc:0x00000000e0b140@(ruby):20 (lambda)>
# => 1

本节原文

yield带参数

def many_yields  
    yield(:peanut)  
    yield(:butter)  
    yield(:and)  
    yield(:jelly)  
end  
  
def test_methods_can_call_yield_many_times  
result = []  
many_yields { |item| result << item }  # result作用域
assert_equal [:peanut, :butter, :and, :jelly], result  
end  

rails中:yield 和 content_for

在View布局中,yield 标明一个区域,渲染的视图会插入这里。最简单的情况是只有一个 yield,此时渲染的整个视图都会插入这个区域:

<html>
  <head>
  </head>
  <body>
  <%= yield %>
  </body>
</html>

布局中可以标明多个区域:

<html>
  <head>
  <%= yield :head %>
  </head>
  <body>
  <%= yield %>
  </body>
</html>

视图的主体会插入未命名的 yield 区域。若想在具名 yield 区域插入内容,要使用 content_for 方法。

<% content_for :head do %>
  <title>A simple page</title>
<% end %>
 
<p>Hello, Rails!</p>

套入布局后生成的 HTML 如下:

<html>
  <head>
  <title>A simple page</title>
  </head>
  <body>
  <p>Hello, Rails!</p>
  </body>
</html>

methods、proc、lambda、block

  • block和proc都不检查参数,methods和lambda会检查参数
  • block和proc是两种不同的东西, block有形无体,proc可以将block实体化, 可以把&p看做一种运算,其中&触发p的to_proc方法,然后&会将to_proc方法返回的proc对象转换成block 。
  • lambda是匿名方法, lambda和proc也是两种不同的东西,但是在ruby中lambda只能依附proc而存在,这点和block不同,block并不依赖proc。
  • lambda和proc之间的区别除了那个经常用做面试题目的经典的return之外,还有一个区别就是lambda不能完美的转换为block(这点可以通过f3和f4执行的过程得证),而proc可以完美的转换为block,注意,我说的lambda指的是用lambda方法或者->符号生成的proc,当然和方法一样lambda是严格检查参数的,这个特点也和proc不一样。

本节详文

闭包(用proc延长变量的生命周期)

有两只途径实现:
闭包创建了它所需要的所有变量的一个备份, 因此是这些副本随着闭包传递.
闭包 延长了它所需要的所有变量的生命周期. 没有复制变量, 而是保留了它们的引用, 而且变量本身不可以被垃圾回收器回收掉.
如果语言支持第一种方式, 那么如果我们创建两个或者更多闭包来访问相同的变量, 每个闭包被调用时都有自己单独对变量的拷贝. 如果语言支持第二中方式, 所有的闭包都引用同一个变量, 它们实际上处理的就是同一变量. Ruby 就是这么做的.看下面的例子:

class SomeClass
  def initialize(value1)
    @value1 = value1
  end

  def value_incrementer
    lambda { @value1 += 1 }
  end


  def value_printer
    lambda { puts "value: #{ @value1 }"}
  end
end

some_class = SomeClass.new(2)
incrementer_closure = some_class.value_incrementer
printer_closure = some_class.value_printer
3.times do 
  incrementer_closure.call
  printer_closure.call
end
运行结果: 
 #=> 
value: 3
value: 4
value: 5

本节原文

类打开,添加新方法,如何删减方法

201706 Ruby 基础 & 元编程