「Rubyで遅延評価」と言えば、Enumerator::Lazy
をよく見かけますが、それ以外の手段を使うことになった話をしてみます。
遅延評価、とは
Rubyを含め多くの言語では、a = foo(b)
のようにすれば、foo(b)
の処理が行われて、その結果がa
に代入される、という挙動になります。これを先行評価といいます。
一方で、Haskellでは、(そもそも変数は再代入不可、関数も結果は引数のみで一意に決まる、という特徴があるからこその話という面も大きいですが)実際にa
の値が必要となるまでfoo(b)
の処理は行われません。これを遅延評価といいます。
遅延評価が便利な面
遅延評価にしてメリットがあることといえば、
- 無限リストや、ものすごく長いリストも簡便に扱える(
Enumerator::Lazy
もこれです) - 引数に渡すために計算はしたけど、関数内で使わなかった…という余計な計算をカットできる(竹内関数では劇的に効果があることで有名です)
- 宣言と評価のタイミングをずらすことができる
今回使ったライブラリ
Rubyの場合、標準ライブラリについているDelegator
のように、ダックタイピングやmethod_missing
を駆使すれば、「他のオブジェクトにメソッドを丸投げする」ようなオブジェクトは比較的簡単に作れます。
今回は、bhuga/promising-futureを使ってみましたが、ソースコードはシンプルなものでした。
使うことになった場面
Railsで検索条件をハッシュ定数に入れて、あとでぶん回す、なんてことをやっていたのですが、条件値の1つとしてscope
を入れていたため、データベースのない環境下では定数定義の段階でDB接続を探してしまって死ぬ、という事態となってしまいました。
しかも、定数を使った構造は変えづらいという事情もあったので、遅延評価して「DBがなくても、実際に使わなければ問題なく通る」という形にしました。さっきのライブラリでKernel.#promise
が生えているので、こんな形となりました。
class SomeCalculator
CALCULATIONS = {
foo: promise { SomeModel.some_cond_scope }
}
end
まあ、本来は定義ごと違う形にすべきなのかもしれませんが、こんな遅延評価の使い方もある、という話でした。