kiyokaのブログアーカイブ

Archive of old blog posts

CRuby 2.1.0の非互換性-Symbolクラスにモンキーパッチできなくなった件

img 忘れないうちに[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?