04

16

[Ruby] yieldの使い道から、yieldの使い方を理解する。

2013.04.16(07:37)

Haskellなどの関数型プログラミング言語で高階関数やラムダ式に慣れていると、Rubyのyieldの理解がやりやすい。Rubyでは無名関数はブロックで記述し、そのブロックを評価しその結果を返すのがyieldである。

ネットで検索すると、yieldの使い方はたくさん見つかる。ここではyieldの使い道を示しながら、yieldの使い方を説明してみよう。

(1) 後始末を忘れずにやりたい。
ファイルはオープンして読み書きするが最後に必ずクローズしなければならない。一般に、「前処理をしたあとは自由に使ってもらっていいけど、最後は忘れずに後始末したい」という場合がある。このときに、yieldを使うと確実に後始末するようにできる。
class File
  class FileDescripter
    def initialize(fname)
      printf "open #{fname}¥n"
      @fname = fname
    end
    def write(msg)
      printf "write #{msg} into #{@fname}¥n"
    end
    def close
      printf "close #{@fname}¥n"
    end
  end

  def self.open(fname)
    fd = FileDescripter.new(fname)
    yield fd
    fd.close
  end
end

File.open("foo.dat") { |fd| 
  fd.write "abc" 
}
実行結果
open foo.dat
write abc into foo.dat
close foo.dat
ファイルをopenするとファイルディスクリプタが返ってくる。そのファイルディスクリプタでwriteすることはユーザーにやってもらうが、使い終わったら必ずファイルディスクリプタをcloseしたい。ブロックで与えられたユーザーのプログラムを評価したあと、自分でファイルディスクリプタをcloseすれば、確実にcloseできる。

yieldが呼び出すブロックが例外を投げてくることを考慮し、確実にfd.closeができるように、さらにFile.openのようにyieldの結果をopenが返すようにすることを考慮して、以下のような実装にする必要がありそう。さらにopenの途中で失敗した場合に例外を投げる処理を追加するなど、実際のソースはさらに複雑になる可能性がある。
  def self.open(fname)
    fd = FileDescripter.new(fname)
    begin
      obj = yield fd
    rescue => e
      raise e
    ensure
      fd.close
    end
    obj
  end


別の記事でExcelのxlsファイルを読むプログラム
http://saltheads.blog134.fc2.com/blog-entry-61.html
を書いたが、xlsファイルはopenするのも長い処理が必要でcloseも忘れずにやる必要がある。その部分を裏側に隠して、見つかったセルの情報だけをyieldに渡されたブロックに渡すことで、ユーザーはやりたいことだけに集中できる。


(2) コレクションがあってその実装を見せずに全部の要素にアクセスさせたい。
Array#eachなどさまざまなコレクションにeachが提供されていて、その要素に順番にアクセスする手段を与えている。自分でも何かのコレクションを含むクラスを作ってeachを定義し、コレクションの実装をみせずに全部の要素にアクセスする機能をユーザーに提供したいときにyieldを使う。要素の個数の分だけ繰り返しyieldを呼出し、各要素をそれぞれyieldの引数に渡す。
class DeviceId
  def initialize(str)
    @str = str
  end
  def each
    array = @str.split(';')
    while e = array.shift
      key,value = e.split(':')
      yield key,value
    end
  end
end

str = "MFG:EPSON;CMD:ESCPL2,BDC,D4;MDL:PM-A900;CLS:PRINTER;DES:EPSON PM-A900;"
d = DeviceId.new(str)
d.each { |key,value|
  printf "#{key} = #{value}¥n"
}

実行結果
MFG = EPSON
CMD = ESCPL2,BDC,D4
MDL = PM-A900
CLS = PRINTER
DES = EPSON PM-A900

データをどのように切り出して要素にするかはユーザーには見せたくない。また要素の個数も何個あるかはわからない。そのようなときに要素を1つずつ取り出して、ユーザーに渡して何か処理をさせたい、そういうときにyieldが使える。上記のように、自分のクラスにもeachを実装して、渡されたブロックに、要素をひとつずつ与えて、要素の個数分だけ、yieldを繰り返し呼び出す。

Rubyプログラマであれば、eachが何をするもので、どのように書けば要素が順番に取り出せるのかは知っているので、自分のクラスも使ってもらいやすくできる。


(3) あとは、、、使い道が見つかったら書く。

プロフィール

島敏博

Shima Toshihiro 島敏博
信州アルプスハイランド在住。HaskellとElixirが好き。組み込みソフトウェアアーキテクト、C++プログラマ、山歩き、美術館巡り、和食食べ歩き、日本赤十字社救急法指導員、インデックス投資、クラシック音楽、SESSAME会員、状態マシン設計、モデル駆動開発、ソフトウェアプロダクトライン、Rubyist、実践ビジネス英語

■ ツイッター
http://twitter.com/saltheads
■ Facebook
http://www.facebook.com/saltheads
■ Qiita
http://qiita.com/saltheads

印刷する場合は、ブラウザの印刷メニューではなく、このページの上から3cmくらいの青いところにある、「印刷」を押してみてください。少しうまく印刷できます。まだ完全ではないのですが、これで勘弁してください。


カテゴリ
最新記事
月別アーカイブ
最新コメント
検索フォーム
リンク
sessame
RSSリンクの表示