学んだことをなぐり書き


Proc.new() と proc() と lambda() の違い

ruby1.9.x では Proc#lambda? を使用して Proc が lambda かどうかを判別することが出来る。
ruby1.9.x では lamdba() の定義に -> 記号を使えるようになった。p = ->(x) { x + 1 }

  • proc()
ruby1.8.x では lamdba() で ruby1.9.x では Proc.new()

  • return
lambda() の return は単に lambda から戻る。
Proc.new() は Proc から戻るのではなく、Procが定義されたスコープに戻る。

  • 引数の数
lambda() は引数の数が違うと ArgumentError を出す。
Proc.new() は多すぎると越えた分を切り落とし、少なすぎると足りない引数に nil を割りあてる。

遅延評価

# 定義された場所では実行されないで呼び出されたときに評価する処理を「遅延評価」と呼ぶ
# ruby には Procクラス が用意されていて call メソッドで処理を呼びだせる。

# hoge はまだ定義されていないが記述(定義)しただけで実行(評価)されてないので、エラーにならない
m = Proc.new { hoge() }

# hogeはまだ定義されていないので呼ぶとエラーになる
#m.call

def hoge() 
  puts 'hoge'
end

m.call
hoge

yield

# メソッドの一部を呼び出すたびに操作したい場合はメソッドにブロックを渡す
# ブロックはメソッドを呼び出した後 do ~ end の中に処理を記述する
def hoge
  puts 'before'
  yield 
  puts 'after'
end

hoge do
  puts 'hoge'
end

# ブロックが渡されていないのに yield を呼ぶとエラー
# hoge

# ブロックが渡されているかどうかをチェックするには Kernel.block_given? を使う
def moke
  block_given? ? yield : puts('no block')
end

moke
moke do
  puts 'moke'
end
before
hoge
after
no block
moke

&修飾 - ブロックをProcに変換

def sum(a, b); yield(a, b); end

# メソッドの引数列の最後に & 付きの引数を用意すればブロックをProcオブジェクトして受け取ることが出来る。
def hoge a, b, &block
  puts block.class
  
  # block は Proc だがブロックとして扱いたい場合メソッドに渡す時に & を付ければ
  # Proc からブロックに変換される。
  puts sum(a, b, &block)

  # block は Proc なのでそのままcallすることも出来る。
  puts block.call(a, b)
end

# ブロックをつけずに呼び出すと sum の yield が失敗する。
# hoge(5, 5)

hoge(3, 2) {|x, y| x * y}
Proc
6
6

ワンコールブロック

class A
  def to_proc
    puts 'call to_proc'
    Proc.new do |x|
      puts x
    end
  end
end

# Procオブジェクトをメソッドの最後の引数に & を付けて渡すとブロックを渡すことが出来る。
# Procオブジェクトでないが、戻り値が Proc の to_proc メソッドもっている場合はProc以外でも渡すことが出来る。
obj = A.new
puts(&obj)

# to_procで生成したProcに引数があれば渡すことも出来る。
[1, 2, 3].each(&obj)

class Symbol
  # Symbolのselfは自分のシンボル文字列が取得できる。
  def show; puts self; end

  # 既に組み込みで定義されているがこんな感じのメソッドが定義されている。
  #def to_proc; Proc.new {|x| x.send(self) }; end
end

:aiueo.show

# 引数を受け取り、その引数のメソッドを呼び出すだけのブロックを「ワンコールブロック」という
p %w{bob bill heather}.map {|x| x.capitalize }

# 上記の Symbol#to_proc を利用するとワンコールブロックを短い記述に置き換えることが出来る。
p %w{bob bill heather}.map(&:upcase)

# 引数が二つ以上の引数を受け取るブロックもサポートしている。
p [1, 2, 3].inject(0, &:+)
call to_proc

call to_proc
1
2
3
aiueo
["Bob", "Bill", "Heather"]
["BOB", "BILL", "HEATHER"]
6

メソッド <-> ブロック の相互変換

class C
  def initialize(value); @x = value; end
  def show; puts @x; end
end

obj = C.new(:hoge)
# Object#method() を呼び出すとメソッドを Method オブジェクトとして取得できる。
m = obj.method :show

# Method#call() で実行できる。
# lamdbaは定義されたスコープ内で評価されるが、Methodは属するオブジェクトのスコープ内で評価される。
m.call # objスコープで評価される。

# オブジェクトからメソッドを引き離すには Method#unbind() を使う
unbound = m.unbind
puts unbound.class

obj2 = C.new(:moke)

# UnboundMethodオブジェクトは実行できないが 
# UnboundMethod#bind() でオブジェクトとひもづければMethodオブジェクトに戻せる。
m2 = unbound.bind(obj2)
puts m2.class
m2.call # obj2スコープで評価される。
puts '----------------'

# MethodからProcに変換したい場合は Method#to_proc() を使用する。
puts m.class
puts m.to_proc.class

# ブロック(Proc)をメソッドに変換する場合は Module#define_method() を使用する。
p = Proc.new { puts "call Proc: #{@x}" }
C.send(:define_method, :show2, p)
obj.show2
obj2.show2
hoge
UnboundMethod
Method
moke
----------------
Method
Proc
call Proc: hoge
call Proc: moke

メンバーのみ編集できます