ピッケル本を読む(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日(土))

  • Rubyイテレータはコードブロックを利用する
    • ブロックはメソッド呼び出しの直後に隣接して書かなければならない
    • ブロック内のコードは、メソッド内でyield文を使うことで呼び出せる
# 簡単な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}
  • Rubyでコードを書けば書くほど、従来のループ構文を使わなくなる。
  • その代わり、コンテナの中身をイテレータで回すクラスを書くようになる。
    • このようなコードは、コンパクトで、読みやすく、保守性が良い

まとめ

短いが、濃い内容の章だったので、少し疲れた。
Procオブジェクトを利用することで実現した「クロージャ」という機能が面白いと思った。クラスにこのクロージャという機能を付加することで、クラスの柔軟性が増してるという認識で良いのかな?