Hatena::ブログ(Diary)

YNote RSSフィード

2009-05-11

Ruby の yield って結局なんなの?

yield って、いまいち分かりにくいですよね。。

わからない⇒調べる⇒忘れる⇒調べる⇒忘れる⇒…

のエンドレスループから抜け出すために、自分なりにまとめることにしました。

ブロック

いきなり yield じゃないやん!という感じですが、我慢して見てみてください。


…じ・つ・は、Ruby のメソッドはすべて「ブロック」を引数にすることができます。

最近知りました(;ω;)。

  def hogehoge( x )
    return x + 2
  end
  p hogehoge( 3 )
  p hogehoge( 5 ){ p "foo" }

ブロックってのは、{ p "foo" } みたいに "{" と "}" に囲まれたやつね。"do" 〜 "end" でもいいみたいだけど。

これを実行すると、

  5
  7

となります。{ p "foo" } はまるっきりシカトです。

(・∀・)

…が、以下のようにすると、様子が違ってきます。

  def hogehoge( x )
    p block_given?
    return x + 2
  end
  
  p hogehoge( 3 )
  p hogehoge( 5 ){ p "foo" }
  --- 結果 ---
  false
  5
  true
  7

あるメソッドにブロックが与えられているかどうかは、そのメソッド内で "block_given?" という値を参照することにより確かめることができます。

では、メソッドに渡されたブロックはいかにしてメソッドに影響を与えることができるのでしょうか?

次のコードを実行してみてください。

  def hogehoge( x, &proc )
    proc.call if block_given?
    return x + 2
  end
  
  p hogehoge( 3 )
  p hogehoge( 5 ){ p "foo" }

結果は次のようになるはずです。

  5
  "foo"
  7

これは、「メソッドにブロックが与えられていれば、そのブロックを実行しなさいよ〜」ということです。

少しややこしいですが、"&proc" というのが「ブロック」を表していて、"&" を取り除いた "proc" が「ブロックを手続き型のオブジェクトにしたもの」を表しています。

ちなみにメソッドを定義するとき、ブロックは必ず最後の引数にしなければなりません。そして上記のように、無きゃ無いで無視されます。このへんはデフォルト引数?に似ていますね。

メソッド定義の詳細は、オフィシャルの以下を参照ください(^_^;;

http://www.ruby-lang.org/ja/man/html/_A5AFA5E9A5B9A1BFA5E1A5BDA5C3A5C9A4CEC4EAB5C1.html#a.a5.e1.a5.bd.a5.c3.a5.c9.c4.ea.b5.c1

ここでは「メソッドへブロックを渡す」ということについて書きました。

大事なのは、以下の 2 点です!

  • メソッドにはブロックを渡すことができる
  • 渡したブロックは、メソッド内で手続き型のオブジェクトとして扱われる

手続き型オブジェクト

まだ yield が出なくてごめんなさい。

まず、手続き型オブジェクト (=Procクラスのインスタンス) について簡単にまとめます。Rubyリファレンスマニュアルによると、

Proc はブロックをコンテキスト(ローカル変数のスコープやスタックフレーム)とともにオブジェクト化した手続きオブジェクトです。

ということです。

さっぱり意味がわかりませんので、例をあげてみようと思います。

prci = Proc.new{ p "hoge" }

p prci.class
prci.call
--- 結果 ---
Proc
"hoge"

こんなふうになります。prci は { p "hoge" } の働きを持つ「インスタンス」で、クラスは Proc であるということになります。

これは以下の表現に似ています。

def prcm
  p "hoge"
end

p prcm.class
prcm
--- 結果 ---
NilClass
"hoge"

二つの動作は同じで、表現方法も似ていますが、prcm は「メソッド」でありインスタンスではありません。prcm というインスタンスは存在しない (nil)prcm は { p "hoge" } の戻り値(nil)を返すので *1ので、prcm.class は NilClass となります。

実際の動作は同じですが、動作させるときには

prci.call # Proc のインスタンス ( 手続き型オブジェクト )
prcm      # メソッド

という違いがあることに注意してください。

メソッドはもちろんですが、手続き型オブジェクトにも引数を与えることができます。以下の例を見てください。

prci2 = Proc.new{|a, b| ( a + b ) }
prci3 = Proc.new{|a, b| ( a + b ); ( a - b ) }

def prcm2( a, b )
  return ( a + b )
end

p prci2.call( 3, 5 )
p prci3.call( 3, 5 )
p prcm2( 4, 9 )
--- 結果 ---
8
-2
13

手続き型オブジェクトを実行したときの値 ( メソッドで言うと戻り値 ) は、ブロック内で最後に評価された値になります。

だから prci2.call( 3, 5 ) の値は 8、prci3.call( 3, 5 ) の値は -2 となるわけですね。

手続き型オブジェクトについて、ざくっとおわかりいただけたでしょうか。

yield

"yield" を英和辞典で引いたときに、Ruby における yield にぴったりくると思われる意味は、

6. 〔ほかのものに〕取って代わられる

これでしょう。先ほどの例を yield を使って表現することができます。

  def hogehoge( x, &proc )      # &proc
    proc.call if block_given?   # proc.call
    return x + 2
  end
  
  p hogehoge( 3 )
  p hogehoge( 5 ){ p "foo" }
  --- 結果 ---
  5
  "foo"
  7
  def hogehoge( x )             # &proc はない!
    yield if block_given?       # proc.call が yield に!
    return x + 2
  end
  
  p hogehoge( 3 )
  p hogehoge( 5 ){ p "foo" }
  --- 結果 ---
  5
  "foo"
  7

前者と後者は全く同じふるまいをします。

つまり、yield は、渡されているブロックと同じ働きをするメソッドのようなものなのです!

  • 引数の部分に &proc を書く
  • proc.call で呼び出す

という動作を、

  • yield で呼び出す

と書くことができるわけです。

この場合、yield は { p "foo" } の処理をする手続き型オブジェクトに「取って代わられる」ことになります。


雰囲気、つかめましたか?


なぜ yield がわかりにくいのか

私が思うに、yield がいまいち理解できなかった理由は、

「メソッド定義の外側にある情報(=ブロック)を暗黙的に利用する」

からだと思います。後者の hogehoge の場合は、「&procてのが渡されてくるねんなぁ」ていうのが定義の中で直感的にわかるんですが、前者のように yield 使っちゃうとブロックが渡されるメソッドなのか、パッとはわからない。

yield に慣れてない人には特にパッとわかりにくい。


lambda とかとのからみ

本当は書きたかったんですが、Proc との厳密な違いとかがひじょーにややこしかったので、やめました(;ω;)。

けど、yield については自分でまとめてみて、理解が深まった気がします。

ツッコミとか、コメントとか、歓迎。

*1:otnさんからコメントにご指摘いただきました!ありがとうございます。

otnotn 2009/05/11 17:25 >prcm というインスタンスは存在しない (nil) ので、

惜しい。
p prcm.class と書くと、prcmメソッドが呼び出され、〜〜つまり、p "hoge"が実行され〜〜値としてnilが返されます。
それに対して、classしてるので、NilClassとなります。
def prcm; 1; end; p prcm.class # => Fixnum

yoshidaayoshidaa 2009/05/11 20:39 おお。なるほど!
戻り値の型ですね…。

ありがとうございました!m(_ _)m

ikasamtikasamt 2009/05/11 23:04 僕もずっとyield苦手なんですよねー。

『はじめてのプログラミング』という本の中では著者は「yeidではなくcall_the_hidden_blockって受け入れられる」と言ってますが、個人的にもyeildという名前だけでは役割が分り難いと思います。

yoshidaayoshidaa 2009/05/12 00:40 ですよねー。
こうやってまとめてみても、すぐ忘れちゃいそうです(^_^;

call_the_hidden_block はそのものズバリですね(笑)
個人的には &proc と proc.call を使っちゃえばよいのでは、という気もしています。

konmamokonmamo 2011/08/04 16:42 雰囲気つかめました^^
ありがとうございます!

nananananana 2012/05/17 10:33 分かりやすかったです

msms 2012/08/14 09:26 最近Ruby学び始め、ここでつまずき困っていました。
大変分かりやすかったです
ありがとうございます

aa 2013/01/06 22:47 とても分かりやすかったです
ありがとうございます

trshugutrshugu 2013/01/30 14:56 ruby yieldで検索してきました。すごくわかりやすかったです!ありがとうございます!


>prcm は { p "hoge" } の戻り値(nil)を返すので

実際にやってみたらp "hoge"からStringが返ってきました。
仕様が変わったのかしら・・・

yoshidaayoshidaa 2013/02/05 23:13 > trshuguさん
こんにちは!コメントありがとうございます。
たしかに。 p "hoge" の戻り値は、
1.9 系?だと "hoge" になっているようですね。
1.8 系?では nil でした(@ω@;

hedgehedge 2013/06/01 12:06 "yieldではなくcall_the_hidden_block" っていうのはわかりやすいですね。

foobarfoobar 2014/02/22 05:47 あんまり本題には関係ないですが、
私は、yield は、どちらかというと、屈するとか道を譲るという意味だと思っています。
要は、自分の本来の処理を差し置いて、ブロックの処理を先にやっているから、相手の要求に屈しているとか、あるいは、どうぞどうぞ的に道を譲るみたいな・・・。

hogepiyohogepiyo 2014/03/09 23:52 ブロック(Proc/lambda)にブロックを渡してもyieldで呼び出すことができないことが惜しまれる…
yieldで呼び出されるブロックはメソッドに渡されたものに限るようですね(v1.9.3です)

hogepiyohogepiyo 2014/03/09 23:52 ブロック(Proc/lambda)にブロックを渡してもyieldで呼び出すことができないことが惜しまれる…
yieldで呼び出されるブロックはメソッドに渡されたものに限るようですね(v1.9.3です)

hagehage 2015/07/08 11:40 ようやくわかった気がします。ありがとうございました。

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証