yieldをどんな時に使うと便利なのか今一つピンとこないので
「こんな時に使うと便利」というサンプルコードがあれば教えてください。
yieldはブロック呼び出しに使います。ブロックはRuby流無名関数(高階関数)です。yieldはブロックを評価した結果を返します。この用法はブロックの評価結果を使用するタイプです。
例えば、コレクションの値の総和や二乗和を求めるようなコードはこんなのです。
module Enumerable def sum if block_given? inject(0) {|s, x| s + yield(x) } else inject(:+) end end end ary = [1, 5, 3] # 総和 ary.sum # => 9 # 二乗和 ary.sum {|x| x*x} # => 35
他にもループを抽象化(自前イテレータを定義)したり、有効範囲を指定(open関数など)したり、条件を指定したり(Enumerable#find)、文脈を変更したり(Object#instance_eval)などです。
inject(:+)はRuby 1.8.7以降で。1.8,6以前ならば inject{|s,x| s+x} で。
有効範囲の指定ならばトランザクション処理を。
def transaction begin setup yield ensure teardown end end transaction { 処理! }
条件を指定するのは、Enumerable#findの兄弟とか。条件を満たす要素とともにインデックスも得る Enumerable#select_with_index を定義してみる。
module Enumerable def select_with_index ary = [] each_with_index do |x, i| ary << [x, i] if yield(x) end ary end end [1, 3, 4, 5, 6].select_with_index {|x| x % 2 == 0 } # => [[4, 2], [6, 4]]
直前の値も欲しい場合は自前イテレータを定義する。
require 'enumerator' # 1.8.7以降は不要 module Enumerable def each_with_previous yield first, nil each_cons(2) {|x, y| yield y, x} end end [1, 3, 4, 5].each_with_previous do |x, prev| [x, prev] # => [1, nil], [3, 1], [4, 3], [5, 4] end
そういえば、Object#instance_evalとかにブロックを渡す場合はyieldは使えないのだった。
たくさんのサンプルありがとうございます。
module Enumerableの中でeach_with_indexや、injectが定義されているのでそのまま呼べばいいわけですね。
参考になりました。
それでは、私がRubyでアプリケーションを作成するときに使った yield のサンプルを挙げます。
例:ブロックを実行する前で処理を行いたい場合
def open_with_lock(path, mode = "r") File.open(path, mode) do |f| f.flock(LOCK_EX) yield(f) end end open_with_lock("/path/to/file") do |f| s = f.read ... end
例:ensureで必ず後始末をしたい場合
def periodic_process File.open("/path/to/timestamp", File::RDWR | File::CREAT) do |f| f.flock(LOCK_EX) begin yield ensure f.rewind f.write(Time.now) f.truncate(f.pos) end end end periodic_process do # ログのロテートなど... end
ありがとうございます。確かに通常の関数より便利そうです。
rubyのコミッターおめでとうございます。
rubikitchさん、いつもありがとうございます。
サンプルの例もすごくいいですが、
>他にもループを抽象化(自前イテレータを定義)したり、有効範囲を指定(open関数など)したり、条件を指定したり(Enumerable#find)、文脈を変更したり(Object#instance_eval)などです。
もしこれらの例もありましたら教えてくださいm(__)m
※あと、サンプルを動かすと以下のエラーになりました
C:/DOCUME~1/mec/LOCALS~1/Temp/rb20A.tmp:15:in `inject': no block given (LocalJumpError)