Memento memo.

Today I Learned.

Effective Ruby 第三章 コレクション まとめ

Effective Ruby 第二章まとめ その2 - Memento memo. の続きです。

第三章はコレクションです。コレクションをうまく扱えるかどうかはコードの可読性・保守性にダイレクトに効いてくる気がするので、言語ごとのクセを把握しておきたいです。

Effective Ruby

Effective Ruby

コレクションを書き換える前に引数として渡すコレクションのコピーを作っておく

  • Rubyのメソッド引数は値渡しではなく参照渡し。例外あり。(Fixnumは値渡しされる)
  • cloneとdupメソッドでオブジェクトのコピー作成ができる。たいていdupを使ったほうがいい。ただしshallow copyになってしまうので注意。
  • dupと比較して、cloneは"レシーバのフリーズ状態"と"特異クラス"を含めてコピーする
  • Marshalでシリアライズ、デシシアライズすればdeep copyになるが、メモリ効率、パフォーマンスの観点が悪いので注意。

破壊的メソッドを使って要素を直接書き換えるようなコードを避ければ大半は回避できそうな気がします。

nil、スカラオブジェクトを配列に変換するにはArrayメソッドを使う

  • Array(foo)とすると、fooがnilでもスカラオブジェクトでも配列に変換できる。
  • Array(foo)のfooにHashを渡してはいけない
foo = nil
puts Array(foo).map(&:to_s)
#=> no error
puts foo.map(&:to_s)
#=> error!!!

ある程度柔軟に引数渡せるようになるのがありがたいです。

要素が含まれているかどうかの処理を効率よく行うために集合を使うことを検討する

  • include?メソッドで要素が含まれてどうかの判定を繰り返し行うような場合は、include?のレシーバをSetクラスのインスタンスにすると高速になる。
  • SetはArrayと同じInterfaceを持つ。
  • require('set') して使う。

Array, HashだけじゃなくてSet, Rangeの存在も忘れないようにしましょう。

reduceを使ってコレクション畳み込む

  • select, mapメソッドよりreduceを使った方がメモリ効率が良い。
  • accumulatorの初期値は必ず与えること(コレクションが空の時の挙動を制御するため)

selectやmapだと効率が悪い、みたいなことが結構書いてあったのですが、内部実装どうなってるんでしょう。確かにreduceはあまり無駄がなさそうですが、向き不向きがある気がするのでいろんなパタンでベンチマーク取ってみようかと思います。

畳み込み自体の概念や実際の使い方については、HaskellやElixirみたいな関数型言語系の解説書を読むと理解が格段に深まるのでElixir本を読みましょう。

Hashのdefault値を利用することを検討する

  • Hashのデフォルト値・デフォルトブロックを使うことを検討する(以下参照)
h = {} # デフォルト値なし
i = Hash.new(0) # デフォルト値あり
j = Hash.new{|hash, key| hash[key] = []} # デフォルトブロック(危険な挙動があるので注意)
  • Hashのキー存在チェックは has_key? メソッドを使う。デフォルト値がnilとは限らないため。
  • デフォルト値より Hash#fetch の方が安全な場合がある
h = {}
h.fetch(:a)
#=> KeyError
h.fetch(:a, "default_value")
#=> "default_value"

Hash#fetch が一番柔軟でよく使う気がします。

Collection classからの継承よりも委譲を使う

  • 継承より委譲を使うこと。
  • 委譲するには require('forwardable') して Forwardable をextendする
  • def_delegatorsクラスメソッドで委譲ターゲットと委譲メソッドを定義する。

"継承より委譲"はよく聞きますね。OOPへの深い理解がないと適切に継承を使うのは難しいです。