ピッケル本を読む(3)第4章 コンテナ・ブロック・イテレータ
メモ
- コンテナとは、1つ以上の他のオブジェクトへのリファレンスを保持するオブジェクトのこと(例えば、配列, ハッシュなど)
- 配列
a = [1, 3, 5, 7, 9] # 開始位置から長さ分を右辺の値に置換 a[2, 2] = 'cat' #=> [1, 3, "cat", 9] # 開始位置の前に右辺を挿入 a[2, 0] = 'dog' #=> [1, 3, "dog", "cat", 9] # 範囲を削除 a[0..1] = [] #=> ["dog", "cat", 9] # 配列のサイズは自動的に調整される a[4..5] = 99, 98 #=> ["dog", "cat", 9, nil, 99, 98] # 範囲指定しないと、配列の代入としてみなされてしまうみたい # a[7] = [99, 98] と同じ a[7] = 99, 98 #=> ["dog", "cat", 9, nil, 99, 98, nil [99, 98]]
今日は、時間が無いのでここまで。また時間ができたら再開する。
再開。(2007年8月2日(木))
- ハッシュの利点
- インデックスにオブジェクトが使える
- ハッシュの欠点
- 要素がソートされないので、簡単にスタックやキューとして使えない
- 複数のキーに同じ値を対応させられない(利点でもあるかも)
- TestUnitフレームワークって何?
ここで一旦中断。なんか、M-fと入力して1語進もうとしたら、ミニバッファに次のように表示されてしまう。
Find files:
とりあえず、Meadowのsite-lispフォルダでこのメッセージを含む.elファイルが無いか検索。
# -Hオプションは、ファイル名を特定するのに必要 $ grep -H "Find files" /cygdrive/c/meadow/site-lisp/* /cygdrive/c/meadow/site-lisp/gtags.el.bak: (setq prompt "Find files: ")
あっ、そうだ、この前「GNU GLOBAL」を入れたんだった…。どうやら、これが原因のようだ。そこで、.emacsに設定されたGNU GLOBALのキーバインドを確認すると、
(global-set-key "\M-f" 'gtags-find-file) ;ファイルにジャンプ
となってたので、この行をコメントアウトして問題解決。人の.emacsを猿真似してばかりだからこんなことになるんだな…。
今日はここまで。夏休みに入ってからの方が忙しい気がする…。
再開(2007年8月4日(土))
# 簡単なyeild文の使用例(イテレータメソッド) def three_times yield yield yield end three_times {puts "Hello"} # 出力結果 Hello Hello Hello
- ブロックの考え方=引数を渡して結果を受け取る
- メソッドに依頼して仕事結果を返してもらうイメージかな?
# フィボナッチ数列の実装 def fib_up_to(max) i1, i2 = 1, 1 while i1 <= max yield i1 i1, i2 = i2, i1+i2 end end # 引数を渡して結果を受け取る fib_up_to(1000) {|f| print f, " "} # 実行結果 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
- ブロックはメソッドに値を返すことも出来る
# Array クラスのfindメソッドは大体こんな感じらしい class Array def find for i in 0..size value = self[i] # ブロックの戻り値を利用して判断 return value if yield(value) end return nil end end # Array クラスが配列の要素を調べるという得意分野を担当してくれるので、 # アプリケーションは用件のチェックに専念できる p [1, 3, 5, 7, 9].find {|v| v*v > 30} #=> 7
- よく使われるイテレータ
- eachメソッドは単純だが重要
- collectメソッドは、コレクションの各要素を受け取り、それをブロックに渡す。ブロックから返された値を使って新しい配列を作る
["H", "A", "L"].collect {|x| x.succ} #=> ["I", "B", "M"]
-
- injectメソッドは説明しにくいのでコードを参照
# sumにinjectの引数、elementに配列の先頭要素が入れられ、 # 2回目以降の呼び出しでは、sumに前回のブロック呼び出しの戻り値が格納される [1,3,5,7].inject(0) {|sum, element| sum+element} #=> 16 [1,3,5,7].inject(1) {|product, element| product*element} #=> 105 # 引数なしで呼び出すと、コレクションの先頭要素が初期値として使われ、 # 繰り返しは2番目の要素から開始される [1,3,5,7].inject {|sum, element| sum+element} #=> 16 [1,3,5,7].inject {|product, element| product*element} #=> 105
# ファイルに自分自身のクローズまでやってもらう # これは便利かも # 実際に便利なのでFileクラスで直接サポートされているらしい class File # *argsは、渡された実引数を配列としてまとめるという意味 def File.open_and_process(*args) f = File.open(*args) yield f f.close() end end File.open_and_process("hoge.rb", "r") do |file| while line = file.gets puts line end end # 出力結果 line one line two line three And so on...
- ここまでの例ではエラー処理を行っていない
- 正しく処理するためには例外処理を使う
- コールバックメソッドって何だろう?
- GUIの処理で自動的に呼び出されるメソッド?(イベントドリブンってやつかな?)
- クロージャ
- 自分が定義された環境が消えてしまっても、元のスコープ情報を使うことが出来る機能らしい
- Rubyではブロックの一塊をまるごとProcオブジェクトとして扱うことでクロージャを実現できる
# クロージャ(初期値nをセットして、Proc#callで当該ブロックを呼び出す) def n_times(thing) # lambdaメソッドはブロックをProcオブジェクトに変換する return lambda {|n| thing * n} end # 初期値23をnにセット p1 = n_times(23) p p1.call(3) #=> 69 p p1.call(4) #=> 92 # 初期値"Hello"をnにセット p2 = n_times("Hello ") p p2.call(3) #=> "Hello Hello Hello"
- クロージャの応用例
- コールバックメソッドを保守性良く、正しく定義したいときに便利
# ボタンを押すとbutton_pressedが呼び出されると仮定する songlist = SongList.new class JukeboxButton < Button # メソッドの最後の引数に&を付けると、Rubyはこのメソッドが呼び出されるたびにコードブロックを探す。 # そのコードブロックはProcクラスのオブジェクトに変換され、この最後の引数に代入される。 def initialize(label, &action) super(label) @action = action end # クロージャを使う利点はたくさんあるみたい(以下に利点を示す) # クロージャの機能を持つProcオブジェクト(ここでは@action)に保持されたコードブロックを呼び出して、柔軟な処理が出来る # 各ボタンクラスごとにこのコールバックメソッドを定義しなくていいので保守が楽になる # コールバックメソッドをボタン自体の機能ではなく、ボタンを使うジュークボックスの機能として正しく定義できる def button_pressed @action.call(self) end end start_button = JukeboxButton.new("Start") {songlist.start} pause_button = JukeboxButton.new("Pause") {songlist.pause}
まとめ
短いが、濃い内容の章だったので、少し疲れた。
Procオブジェクトを利用することで実現した「クロージャ」という機能が面白いと思った。クラスにこのクロージャという機能を付加することで、クラスの柔軟性が増してるという認識で良いのかな?