RubiniusのArray#productをブロック付きで呼んだら…
自作Gemのテストをしている過程で、Rubiniusのバグを掘り出してしまうハメになりました。
Rubiniusって?
Rubiniusは、Rubyの実装の1種です。特徴としては「スピード志向」ということと、「大半の部分がRubyで書かれている」ということがあります1。標準実装のMRI(CRuby)、Java上での実装のJRubyと並んで、そこそこメジャーなRuby処理系の1つです。
Array#product
とは
Array#product
は、複数の配列について要素同士の組み合わせを全生成するメソッドです。ブロックを渡した場合、組み合わせの1つ1つについてブロックを実行する、というようになっています。
気づいたきっかけ
自作のGemが完成に近づいてきたため、もともと開発に使っていたRuby 2.2以外に、古いバージョンやJRuby、Rubiniusでもテストを回していました。そして、このGemは内部にいくつかのクラスを含んでいて、そしてそれら同士の演算についても定義していたので、組み合わせでテストしようとArray#product
を使って組合わせを展開するようなテストコードを書いていました。
describe SomeClass do
terms1 = [a, b, c, ...]
terms2 = [d, e, f, ...]
terms3 = [g, h, i, ...]
terms1.product(terms2) do |term1, term2|
context "#{term1} and #{term2}" do
# 略
end
end
terms1.product(terms3) do |term1, term3|
context "#{term1} and #{term3}" do
# 略
end
end
end
MRIでは問題なくテストを通過したので、Rubiniusで動かしてみたのですが、一気に赤字でエラーを連発しだしてしまいました。調べてみると、そもそもcontext
の時点で余計なものが現れていることに気づきました。RSpecとRubiniusの組み合わせで起こる問題かと思ってさらに追いかけてみたのですが、試しにterms1
をfreeze
してみたところ、MRIは何事も起こらなかったのに対して、RubiniusはArray#product
内部でfrozenな配列を書き換えようとしてRuntimeError
となってしまいました。
最低限の再現コード
arr = [1,2,3].freeze
arr.product([4,5,6]){ 1 }
# MRIでは無問題、RubiniusではRuntimeError
原因の追求
スタックトレースにRubinius内のRubyのコードも出ていたので、追いかけるのはそこまで困難ではありませんでした。で、実際のコードを見てみると、なぜか返り値としてself
に書き込みを始めるし、ブロックを取るときにもいったん配列を生成するようになっている(動作はするのですが)など、これでいいのか感満載の実装となっていました。
そのままの勢いでIssueを立てて、とりあえず様子見中です。
まとめ
オープンソースプロダクトを使っていろいろ構築していくと、時折バグに出くわすことがあります。プルリクエストを作るにはハードルが高くても、Issueは事象をまとめればいいので、ぐっと立てやすいものです。せっかく万人に開かれているオープンソースの世界ですから、飛び込んでみたほうが、きっと新たな局面が開けます。
-
このため、Rubyの標準メソッドについて「Ruby内で書くにはどうすればいいか」という事を調べるにも便利です。 ↩
Author And Source
この問題について(RubiniusのArray#productをブロック付きで呼んだら…), 我々は、より多くの情報をここで見つけました https://qiita.com/jkr_2255/items/b3d1fcb3f3a3d6b0df2c著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .