CRuby 2.1.0の非互換性-Symbolクラスにモンキーパッチできなくなった件
忘れないうちに[Nendo]をCRuby 2.1.0で動かすのに苦労した話を書いておこう。
多分、SymbolにモンキーパッチしているプログラムはCRuby 2.1.0で動かなくなるので、その時に誰かの役に立つかもしれない。
CRuby 2.1.0に持ち込まれた非互換
.以下は、CRuby 2.0.0で動いて、CRuby 2.1.0で動かないコード
# Symbolへのモンキーパッチ
class Symbol
def set_lineno(value)
@lineno = value
end
def lineno
return @lineno
end
end
sym1 = :a
sym1.set_lineno( 10 )
p sym1.lineno
sym2 = :a
sym2.set_lineno( 20 )
p sym2.lineno
– 結果(ruby-2.0.0-p353)
$ ruby symbol.rb
10
20
– 結果(ruby-2.1.0)
$ ruby symbol.rb
symbol.rb:5:in `set_lineno': can't modify frozen Symbol (RuntimeError)
from symbol.rb:13:in `<main>'
何がしたかったのか
[Nendo]はRubyで実装したScheme処理系(サブセット)である。 [Nendo] 0.6.6ではSchemeのソースコードをパースしたツリーの内部で、SchemeのシンボルはRubyのSymbol型に変換して保持していた。 そうするメリットは絶大で、Schemeの処理系の中で:quoteのようなRubyのSymbolと == 演算子で比較できる。 また、同時にそのシンボルがSchemeプログラムのソースコードのどの位置(ファイル名、行番号)に出現したかを保持したい。 つまり、同じSymbol型でも、位置情報を持っているものと持っていないものがあり、かつ、同じSymbol型として操作できる。 Ruby 2.0.0までは、乱暴だが上のようなモンキーパッチを使うことで、このトリックを実現していた。
Ruby 2.1.0
Ruby 2.1.0では上記のようなモンキーパッチは許されなくなった。 最適化のためか、はたまた安全性のためか、その両方かもしれないが、そのような挙動になった。 puppetも同様の問題でひっかかってpuppet側で対応したようだ。 (maint) Fix can’t modify frozen Symbol error on Ruby 2.1.0 by jeffmccune · Pull Request #2184 · puppetlabs/puppet · GitHub
Nendo 0.6.8
[Nendo]も同様に0.6.8で地道に対応した。 Symbolへのモンキーパッチをやめて、ParsedSymbolという型を新規に作り、RubyのビルトインSymbolとの比較箇所などを地道に修正していった。 影響範囲は大きく、なかなか骨の折れる作業だった。
感想
本来はモンキーパッチは良くないコーディングだとわかっていたが、このような逃げを許してくれるのがRubyの良いところかと思っていた。 多分、モンキーパッチを許さないことで最適化しやすくなり実行効率は高まりそうな気がするので、CRubyの進化として良いことだろう。 ただ、ちょっと固くるしいなぁと思わなくもない。 言語が洗練するとはこういうことなのかなぁ。
追記 ( 2/23 )
SymbolもGCできるようにする改善だったようだ。それなら従おう。 Feature #8906: Freeze Symbols - ruby-trunk - Ruby Issue Tracking System Now, Integer and Float objects are frozen objects. How about to freeze Symbol objects, too? I think Symbol is friend of Integer. … Background of this proposal:
Now, I’m working on “GC-able Symbols” feature. Freezing symbols make this feature easier.
for example: (1) set an instance variable @iv for symbol s (2) collect s (3) generate s (4) what value of @iv for s returns?