人力検索はてな
モバイル版を表示しています。PC版はこちら
i-mobile

Rubyのyieldを使った例を教えてください


yieldをどんな時に使うと便利なのか今一つピンとこないので
「こんな時に使うと便利」というサンプルコードがあれば教えてください。

●質問者: garyo
●カテゴリ:コンピュータ ウェブ制作
✍キーワード:Ruby コード
○ 状態 :終了
└ 回答数 : 3/3件

▽最新の回答へ

1 ● るびきち
●35ポイント

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)などです。

http://www.ruby-lang.org/ja/man/html/index.html

◎質問者からの返答

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)


2 ● るびきち
●23ポイント

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は使えないのだった。

http://d.hatena.ne.jp/rubikitch/

◎質問者からの返答

たくさんのサンプルありがとうございます。

module Enumerableの中でeach_with_indexや、injectが定義されているのでそのまま呼べばいいわけですね。

参考になりました。


3 ● 高尾宏治
●22ポイント

それでは、私が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

私の日記: http://d.hatena.ne.jp/kouji0625/

◎質問者からの返答

ありがとうございます。確かに通常の関数より便利そうです。

rubyのコミッターおめでとうございます。

関連質問


●質問をもっと探す●



0.人力検索はてなトップ
8.このページを友達に紹介
9.このページの先頭へ
対応機種一覧
お問い合わせ
ヘルプ/お知らせ
ログイン
無料ユーザー登録
はてなトップ