ayuminのあまり更新しないBlog

筆不精なのでめったに更新しません

Rails勉強会@東京第41.1回にいってきた

前回のRails勉強会はインフルエンザ対策のため中止になってしまったので今回は2ヶ月ぶりの開催。
前日にデザインパターン再入門勉強会の主催をしてカナリ疲れたので、あんまり何も考えずに寝ぼけ眼で会場に到着。
到着時にすでに2つのセッションが立ち上がっていました。
参加したのはペアプロ実習。セッションオーナーはt-wadaさん。たまたま同じくらいのタイミングで到着したa_matudaさんとペアを組む。

お題:LRUHashの実装

仕様
  • 要素サイズに限界があるHash。
  • 最も使われていない要素が削除されていく。
ペアプロ開始

まずは仕様をRSpecで書くか、Test::Unitでかくかみたいな話をしたが結局どのペアもRSpecで書くことにしたようだ。
とりあえずlru_hash_spec.rbを作る。

touch lru_hash_spec.rb

で、仕様を書くわけだが最近のRSpecでつかえるようになったsubjectメソッドを使ってみようということになった。

require 'lru_hash'
describe LRUHash do
  subject { LRUHash.new }
  it { should be_a_kind_of Hash }
  it { subject.limit.should == 3 }
end

このsubjectというメソッドは、ブロックの評価結果をsubjectという名前のローカル変数に設定してくれるようで、さらにexampleの中でレシーバーを省略した場合には自動的にsubjectがレシーバーになるように何かしてくれるというもの。これによってなにができるかというと、exampleの記述がより自然言語に近くなる。例えば上の例で最初のexampleは

  it "should be a kind of Hash" do
    LRUHash.new.should be_a_kind_of Hash
  end

とか

  before { @lh = LRUHash.new }
  it "should be a kind of Hash" do
    @lh.should be_a_kind_of Hash
  end

と記述していたが、subjectによってかなりシンプルになっている上に英語としてふつうに読み下せるようになっている。
しかし、あくまで省略可能なのは呼び出すメソッドがshould とか should_not みたいな引数にマッチャをとるような場合のみらしく、subjectがもともと実装しているメソッドを呼ぶためには2番目の例のように明示的にレシーバーにsubjectを指定してあげる必要がある。

describeのネスト

describeはネストされるのが常だが、あまり階層が深くなると読みづらい。どういう基準でネスト/外だしにするかは悩むところでもある。
subjectが言及する主語が変化するかどうかというのがひとつの指標になりそうだということがわかった。a_matsudaさんと試してみてわかったことだけど、例えば

  before do
    @h = Hash.new
    @h[:a] = 'apple'
    @h[:b] = 'beacon'
    @h[:c] = 'cucumber'
    @h[:d] = 'dicon'
  end
  subject { @h[:a] };it { should == 'apple' }
  subject { @h[:b] };it { should == 'beacon' }
  subject { @h[:c] };it { should == 'cucumber' }
  subject { @h[:d] };it { should == 'dicon' }

は期待通りに動作しなかった。どうやらexampleの前に先にすべてのsubjectたちが実行されてしまうため、すべてのexampleは同じsubjectでしか実行できないみたい。上の例だとどのexampleも@h[:d].should == 'xxx' という呼び出しになっていまう。そんなわけで(逆説的だが)一つのdescribeが定義する領域内ではひとつのsubjectしかありあえないのだからsubjectが変化するごとに describeを切るというのが良いということになりそう。

実装コード

途中は全部省略しちゃうけど、実際はテストとコードを行きつ戻りつ進めていって一応時間内にお題をクリアすることはできた。
a_matsudaさんらしく積極的にActiveSupportを利用することに話もおちついたのでOrderedHashを使って実装は非常に簡単だった。

require 'rubygems'
require 'active_support'

class LRUHash < ActiveSupport::OrderedHash
  def limit; 3 end
  def []=(key,value)
    super
    shift if size > limit
  end
  def [](key)
    returning super do
      @keys.push @keys.delete(key)
    end
  end
end

参照系の[]メソッドをオーバーライドするときに、OrderedHashが内部的に利用しているインスタンス引数の名前を知る必要があったのだが、irbをつかってサクっと見つけることがでるあたりさすがに手慣れたもんだと感心しました。

もうひとつはreturningメソッド。これは知らなかったけどActiveSupportの機能の一つで、Ruby1.8.7以降にも同じ機能を提供するtapメソッドがあるらしい。メソッド名の違いはDHHとMatzの思想の違いからきているのではというのがのちのmoroさんの解説であった。このメソッドは要は

def tap &block
  yield self
  self
end

をしてくれるものらしい。つまりオブジェクトの中身をちょっと覗きたいけど、評価結果は変えたくないというときにつかう。デバッグの時とかに便利だよね。永和ではtapp なるメソッドが常用されているそうで、これは

>|ruby|
def tapp
self.tap {|obj| p obj }
end

<というものだそうだ。

生みの親からツッコミが入ったので訂正

def tapp
  require 'pp'
  tap { pp block_given? ? yield(self) : self }
end

だそうです。


財布

帰りに財布をなくした!と大騒ぎをしていましたが、とんだマヌケでカバンを2つ持ってきていたことをすっかり忘れてたorz。
もう片方のカバンの方にちゃんとはいっておりました。お騒がせしてすみませんでした。。。