結城浩のSICP日記 - クイズ とそれへの shiro さんのコメントを読んでいて、Squeak や VisualWorks の Smalltalk でも似たようなことがあるな…と思いました。
後述の VisualWorks の場合とは異なり、Squeak の Smalltalk では、リテラルの書き換えも自由に行なえるため、くだんの hyuki さんのエントリーの quiz を施したのと同種の挙動をさせられます。
Object >> foo ^ #(0)
Object new foo " => #(0) "
Object new foo at: 1 put: 1. Object new foo " => #(1) "
ところで、LISP や Scheme と違い、メソッド(つまり、関数)が必ずクラスに属し、そしてクラス以外には属さないことが定められている Smalltalk では、この制約を利用して、メソッド定義時のソースをキャッシュしておき、いつでも参照できるようにする仕組みが設けられています。システムブラウザなどでのソース閲覧も、原則としてこの機構を用いています。
しかし、キャッシュという性格上、前述のような操作の結果はソース閲覧時には反映されません。
(Object >> #foo) getSourceFromFile " => 'foo ^ #(0)' "
したがって、もし、前述のような操作の結果により問題が生じていた場合でも、その原因究明に思わぬ時間を費やしてしまったりします。
なお、実際を反映した結果を得ることそれ自体は、メソッドの逆コンパイルにより可能です。
(Object >> #foo) decompileString " => 'foo ^ #(1)' "
システムブラウザで同じことをしたければ、ウインドウ中段のオプションボタン群右端にある、普段は source とラベルされた What to show ポップアップから decompile を選んでおきます(Squeak の場合)。その都度、デコンパイルした結果がコード表示枠に呼び出されます。
もちろん、hyuki さんの出題にもあるように、対象となる配列などをリテラルではなく、動的に生成するようあらかじめ定義しておけば、こうした“問題”の回避は可能です。
Object >> bar ^ Array with: 0
Object new bar " => #(0) "
Object new bar at: 1 put: 1. Object new bar " => #(0) "
あえて、リテラル記述の利便性をとるなら、コピーを返すようにすればよいでしょう。
Object >> baz ^ #(0) copy
Object new baz " => #(0) "
Object new baz at: 1 put: 1. Object new baz " => #(0) "
なお、ユーザーに多くの自由を許す Squeak と異なり、VisualWorks の Smalltalk では、リテラル式で生成された配列や文字列はイミュータブルなので、それへの破壊的操作はエラーになります。
#(0) at: 1 put: 1 " => Unhandled exception: Core.NoModificationError "