名古屋で数学するプログラマ(仮)

@antimon2 が趣味兼一部本職の数学で何かするときのブログ。

enumerable_lz 0.1.5 公開しました。

なんか今更感ハンパないですけれど。
約 3 年ぶりに、自作の gem の新バージョンを公開しました。
もう誰も使ってないかもしれないにもかかわらずw

enumelable_lz 0.1.5

簡単に言うと、Enumerable に遅延リストを返すフィルタリングメソッド Enumerable#filter と変換メソッド Enumerable#transform を追加する gem です。
Ruby 2.0.0 で導入された Enumerable#lazy と同じような機能を提供します。
でも Enumerable#lazy の挙動がどうしてもなじめなくて、メンテナンスリリースの意味も込めてアップデートしちゃいました。

【2014/04/23 23:33 追記あり】

特長

もう今更感満載なので簡単に列挙しておきます。

  • Enumerable#filter は、Enumerable#selectEnumerable#grep の遅延バージョンです。
    • Ruby 2.0 以降の Enumerator::Lazy#select とほぼ同様の動作をします。
  • Enumerable#transform は、Enumerable#map(別名 Enumerable#collect)の遅延バージョンです。
    • Ruby 2.0 以降の Enumerator::Lazy#map とほぼ同様の動作をします。
  • 少し古い Ruby(1.8.7, 1.9.x)でも動作します。
  • cRuby 以外にもいくつか対応しています(テストしているのは JRubyMacRuby の最新のみ)。
  • Ruby 2.0 の Enumerable#lazy よりは速いです。
  • 《New for 0.1.5》〜.filter.with_index{|el, i| 〜 } というメソッドチェインができます。
    〜.select.with_index{|el, i| 〜 } と同様に動作し、即実行されて結果の配列を返す代わりに、遅延実行される遅延リストを返します。
  • 《New for 0.1.5》〜.transform.with_index{|el, i| 〜 } というメソッドチェインができます。
    〜.map.with_index{|el, i| 〜 } と同様に動作し、即実行されて結果の配列を返す代わりに、遅延実行される遅延リストを返します。

特に最後の 2 つ。
例えばこんなコードを考えてみてください*1

require 'enumerable_lz'

(1..99999999).select.with_index{|n, i| i.odd?}.take(10)      #1
(1..99999999).filter.with_index{|n, i| i.odd?}.take(10)      #2
(1..99999999).lazy.select.with_index{|n, i| i.odd?}.take(10) #3

見た目よく似た 3 つのコードですが。
#1 は、先に全要素を算出してしまうので、環境にもよりますが数秒〜数十秒待たされますが、その上で結果としては正しい結果(=[2, 4, 6, 8, 10, 12, 14, 16, 18, 20])が得られます。
それに対して #2 は、遅延リストの特性で必要な要素のみ算出するので、一瞬で正しい結果が得られます。
では、 #3 はというと。なんと ArgumentErrorになります(Ruby 2.0.0/2.1.0 ともに)。このようなメソッドチェインが許されていない(より正確には .lazy.select がブロックを省略できない)んですね。

実は今回修正するまで、#2 も正常に動作しませんでした(^-^;
.with_index が遅延リストにもならなかった上にフィルタリングも動作していなかった(^-^;
#3 を先に試して「これはないな」と思った直後に #2 試したらそのざまだったので、思い立って修正リリースしたというわけです。

ま、どれだけ需要があるか全く期待できませんけれども。
.lazy.select.with_index{ 〜 } がエラーで動かないことにお困りの方。
.select.with_index{ 〜 } の便利さを知っていて、その遅延リスト版をお探しの方。
もしよろしければ、enumerable_lz 0.1.5 お試し下さい。

ていうか、.lazy.select とかtoka .lazy.map とかが、ブロックが渡されなかったら Enumerator::Lazy を返すようになれば解決するのかも?

【2014/01/09 18:00 追記】
@cielavenir さんがサンプル作って検証して下さいました。

http://qiita.com/cielavenir/items/7c0ba3ab1cd91f06952e

lazy_select.rb
classEnumeratorclassLazyalias:select_without_block:selectdefselect(&block)returnto_enum(:select)if!block_given?select_without_block(&block)endendendp(1..999999999).lazy.select.with_index{|e,i|i.odd?}.take(10).to_a


ruby1.9 -v
ruby 1.9.3p194 (2012-04-20) [i686-linux]
ruby1.9 -rbackports lazy_select.rb
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
ruby2.0 lazy_select.rb[BUG] vm_call_cfunc - cfp consistency error
ruby 2.0.0p247 (2013-06-27 revision 41674) [universal.x86_64-darwin13]
いやですね、本当は http://antimon2.hatenablog.jp/entry/2014/01/08/235706 への肯定的な解答にしたかったんですよ( https://github.com/yhara/enumerable-lazy/blob/master/lib/enumerable/lazy.rb でもantimon2/enumerable_lzに謝辞書かれてますし )。
しかしまさかのクラッシュ…。
PS
Lazy.class_eval{}とすれば、ruby1.9 -renumerable/lazyでも期待通りの動作をします。

Evernote 貼り付けってばほぼただのテキストになるのね(>_<))

手元の環境でもいろいろ試してみたところ、Ruby 2.0.0 の場合は、Bus Error とか Segmentation Fault とか、名前は違えどやはりとにかくクラッシュすることに変わりは無いようでした(>_<)
なお、Ruby 2.1.0 ではクラッシュせず期待通りに動作することを確認しました。

【2014/04/23 23:33 追記】
Ruby 2.0.0-p451で修正されたそうです。

*1:あまり意味の無いコードですけれど、あくまで結果が良く分かる動作例ということで。