ミニパターン集

$Id: minipattern.rd,v 1.8 2003/07/29 15:55:24 aamine Exp $

いかにも Ruby 特有ぽいものを中心に小技をあつめました。 Cookbook なんかと違うのは、具体的な課題を解決する方法ではなく 言語上のパターンにしぼったこと。

インスタンス変数の遅延初期化

初期化されているかどうかわからない インスタンス変数を初期化するときは ||= を使う。

@ivar ||= ""    # @ivar が既に非 nil ならばそのまま、nil なら "" を代入

1.6.2 まではこう書くと初期化されていない場合に警告が出ていたのだが、 1.6.3 からは ||= を使う場合に限り警告が出なくなった。 Mix-in のメソッド中での初期化などに便利である。

コンテナの遅延初期化

例えば配列のハッシュを作るとき、ハッシュのキーになにが来るか わからないとしよう。すると、最初から全てのキーに対して 配列を入れておくことができないので、とあるキーが最初に 登場したときに配列をセットしないといけない。以下はそれを 一発でやるコード。

table = {}
(table[key] ||= []).push val

二行目の括弧の中がポイント。同様にハッシュの配列なら

list = []
(list[i] ||= {})[key] ||= val

とできる。二行目は、テーブルを作ると同時に val を返す という点も重要だ。また val が一回しか出ていないので、 変数のみならず、コストのかかる計算や副作用のある式も置ける ところが便利である。

1.8 からはブロック付きの Hash.new を使うこともできる。 引数に渡すのとブロックを使うのでは意味が違うことに注意。

table = Hash.new {|h,k| h[k] = [] }
table[key].push val

# 以下は間違い
table = Hash.new([])
table['a'].push 1
table['b'].push 2
p table['b']        # [1,2]

基本的には initialize でデータ構造を集中的に指定することができるぶん ブロック付き Hash.new のほうが好ましいはずである。 なのであるが、どうも好きになれない。 なぜ好きになれないかと言うとそれはたぶん、 デフォルト値の配列がどこから湧いてくるのか絵的にイメージできないからだと思う。 一般のクラスだったらデフォルト値生成用の Proc を持っていても 納得できるのだけど、どうも Hash だと Proc ブロックを持つほどの 「隙間」が絵として存在しないような感じがしてしまう。 なにしろ Hash はリテラルがあるから、 絵的にリテラルがそのまま脳内でも展開されているようなのだ。 でもって Hash リテラルを使う場合は Proc が入る余地はない。 従って納得できない、のである。

そういうわけで自分ではあまり Hash.new は使いたくないのだが、 そんなの気にならん、という人は心おきなく Hash.new { } を使ってもらえばよいと思う。

バージョン比較

最も簡単なのは String#> を使うことである。 ただしどこかの桁が二桁以上になると通用しなくなる。

'1.2.6' > '0.9.0'

Ruby のマイナーバージョン/パッチレベルに限って言えば、 二桁にならないことは保証されている。 メジャーバージョンはわからない。

暫定 inspect

デフォルトの inspect はインスタンス変数の中身まで 再帰的に表示するので大きいオブジェクトを p すると かなり悲惨なことになる。 そういうときはとりあえずこうしておこう。

def inspect
  "#<#{self.class}>"
end

ちなみに 1.8 からは pp が使えることもお忘れなく。

ブロック丸投げ

こんなの常識だが念のため書いておく。

def someiter( &block )
  @array.each(&block)
end

イテレータから変数を介さず結果を得る

File.open(fname) {|f| str = f.read }
print str

は先に str に代入しておかないとエラーになる。いっそのこと

def read_all( fname )
  File.open(fname) {|f| return f.read }
end

print read_all(fname)

としよう。

また File.open に限って言えば 1.8 以降は

File.read(fname)

が最初から使える。

map + compact

必要なものだけ map する。

enum.map {|i| cond(i) ? edit(i) : nil }.compact

別解

enum.select {|i| cond?(i) }.map {|i| edit(i) }

なんとなく後者のが格好よさげである。

map + flatten

配列をつなげた大きな配列をつくるとき、 (以下の #edit は配列を返すとして)

result = []
enum.each {|i| result.concat edit(i) }
result

と書くかわりに、map して flatten できる。

result = enum.map {|i| edit(i) }.flatten

さらに 1.8 以降で使える別解

result = enum.inject([]) {|a,i| a << i }

ちなみに Haskell だと同様の働きをする Data.List.concatMap という関数が存在する。

繰り返しを配列に変換

dosomething arg1
dosomething arg2
dosomething arg3

[arg1, arg2, arg3].each {|a| dosomething a }

と変換できる。

特に

dosomething 'this'
dosomething 'is'
dosomething 'a_pen'

%w( this is a_pen ).each {|s| dosomething s }

と変換できる。

オブジェクトの内容で行動を分岐

case str
when 'some' then ....
when 'any'  then ....
else
  ....
end

def ???
     :
  __send__ 'do_' + str
     :
end

def do_some() .... end
def do_any() .... end

と変換できる。

case をハッシュで置きかえ

さらにハッシュを使って変換を複雑にできる。

TABLE = {
  'some'       => 'some',
  'alias_some' => 'some',
  'any'        => 'any',
  'alias_any'  => 'any'
}
       :
  __send__ 'do_' + TABLE[str]
       :

def do_some() .... end
def do_any() .... end

クラスをまたいだ「内部」実装

複数クラスにまたがってひとつのインターフェイスを 実装したいのだが、その実装は外に見せたくない。

→ 実装用メソッドを private にして __send__ で呼ぶ。

class A
  def some( b )
    b.__send__(:any)
  end
end

class B
  private
  def any
    ....
  end
end

new の引数からクラスをダイナミックに決定

かなり作ったような例だが。

class Header

  # 注: このクラスはどれも Header を継承している
  TABLE = {
    'date'   => DateHeader,
    'to'     => MultiAddressHeader,
    'sender' => SingleAddressHeader,
    'mime-version' => MimeVersionHeader,
         :
  }

  class << self
    alias newobj new
  end

  def Header.new( headername, body )
    klass = TABLE[arg.downcase] or raise ArgumentError, 'unknown name'
    klass.newobj headername, body
  end

end

newalias して呼ぶところがポイント。 ここで super を使ったりすると無限ループになる。

クラスメソッドを alias する

class Module
  def alias_class_method( new, old )
    instance_eval "alias #{new.id2name} #{old.id2name}"
  end
end

そのつど

class << self
  alias newname oldname
end

と書いても全然かまわないが三行必要なのが嫌い。

イテレータ←→メソッド 変換

def some_iter
  yield arg
end

some_iter do |arg|
  ....
end

このようなイテレータは次のように書き換えられる。

def some_iter( obj, mid )
  obj.__send__(mid)
end

class SomeClass
  def m( *args )
    ...
  end
end

some_iter(SomeClass.new, :m)

こうでもよい。

def some_iter( method )
  method.call
end

class SomeClass
  def m( *args )
    ....
  end
end

some_iter(SomeClass.new.method(:m))

任意のオブジェクトのインスタンス変数を取る

instance_eval の最も役に立つ使いかた。

val = obj.instance_eval { @ivar }

これ以外の目的にはあまり instance_eval を使わないほうがよい。

任意のオブジェクトの特異クラスを得る

metaclass = (class << obj; self end)

ただし、それまで obj に特異クラスがなくても これをやった時点で自動的に特異クラスを生成してしまうので注意。

break を検出

def breaked?( proc )
  checkbreak(&proc)
end

def checkbreak( &block )
  brk = true
  begin
    yield
    brk = false
  ensure
    return brk
  end
end

pr1 = proc { break }
pr2 = proc {  }
pr3 = proc { next }

p breaked?(pr1)
p breaked?(pr2)
p breaked?(pr3)

たしかに break は検出できるのだが、 ブロック中からの return も検出してしまう。困った。

メソッド名の変更に追従する

例えば 1.6Array#filtercollect! と改名され、 filter を使うと警告が出るようになった。 この警告を回避しつつ互換性を保つには、 以下のようにして必要なときだけ Array#collect! を定義し、 コードでは collect! だけを使うようにする。

unless [].respond_to?(:collect!)
  class Array
    alias collect! filter
  end
end

respond_to? を使うところと、unless の中に class 文が存在できる というところがポイント。

クラスを alias

クラス名が定数だってのは常識。 ということは定数を定義すればクラスに別名をつけられるのも常識。

ClassAlias = SomeClass

以下のように、エイリアスに対して class 文を適用することも可能。

class C
  p self      # C
end

ClassAlias = C

class ClassAlias
  p self      # C
end

定義されているかもしれないクラスを定義

例外クラスでおこりやすい。

unless defined? ParseError
  class ParseError < StandardError; end
end

いきなり class ... end と書いても、 スーパークラスが同じか省略されている限り問題はないのだが、 このほうがやりたいことがより明確になると思う。

拡張モジュールと Ruby と両方で同じクラスを提供する

例えば高速な C 版と移植性の高い Ruby 版を両方提供したいという場合、 次のような型にはめるとよい。

# somelib.rb
require 'somelib_r.rb'        # SomeClass_R を定義
begin
  require 'somelib_c.so'      # SomeClass_C を定義
  SomeClass = SomeClass_C
rescue LoadError
  SomeClass = SomeClass_R
end

つまり、常に両方のクラスをロードしておいてクラス名で分岐する。 エラーが起きたときにクラス名だけ見ればどちらの バージョンを使っているかわかるのでデバッグに便利だ。