レガシーコード生産ガイド

私に教えられることなら

Squeak Smalltalkでクラスメソッドとインスタンスメソッドを相互に移動/コピー

明け方、早くに目が覚めたのでSqueakを探検して、ブラウザのメニューを片っ端から調べた。

いろいろ便利なの見つけたんだけど、前々から欲しかったタイトルの機能が無かったので作った。

もうとっくに同じ機能あったり、他にうまいやり方なんかあったら教えて下さい。

ブラウザのコンテキストメニューを編集する

Morphicをinspectしていくのが簡単だろうけど、せっかくmethod string with itメニューを見つけたので、それで調べる。

コンテキストメニューの適当なラベルをワークスペースなどに書き写し、選択してコンテキストメニューmore...method string with itで検索。

いくつかヒットするので、まず一番あやしいBrowser>>#mainMessageListMenu:を書き換えると、正解だった。

次のように書き換えると

f:id:phaendal:20151130055952p:plain

メニューが追加される。

f:id:phaendal:20151130060142p:plain

どこのクラスにメッセージが送られるかわからないので、とりあえず追加したメニューを選択して送信してみる。デバッガが開いてBrowserクラスに送られていることがわかる。

Browserクラスにメソッドを作る

  1. インスタンス変数、クラス変数どちらが選択されているのか取得し、前者ならクラスに、後者ならメタクラスに対して
  2. メソッド追加メッセージを送ると良さそう

1について、metaClassIndicated変数などを使ってメソッド書いてたけど、途中でBrowser>>#selectedClassOrMetaClassがあることに気づいたのでそれを使う。

2については、selectedMessageNameインスタンス変数がそれそのものだった。

Browser >> copyClassOrInstMethod
    self selectedClassOrMetaClass
        copyClassOrInstMethod: selectedMessageName.

Browser >> toggleClassOrInstMethod
    self selectedClassOrMetaClass
        toggleClassOrInstMethod: selectedMessageName.

メソッド移動メソッドを書く

メソッドを追加するメソッドとして、ClassDescription>>#addAndClassifySelector:withMethod:inProtocol:notifying:を見つけた。プロトコロルも保ちたいのでこれが良さそうだ。

withMethod:の部分に渡すのはcompiledMethodなんだけど、再コンパイルする必要は無いのか気になった。Squeakバイトコードを少し見たところ、メソッドの引数やローカル変数は定義順のindexでアクセスしている。問題となるのはインスタンス変数だけど、それはコンパイル時に警告が出るのでそもそもこの機能を使う必要は無さそう。メッセージングはLateBindingなので問題は無いでしょう。というわけでコンパイル済みのインスタンス/クラスメソッドをそのまま使う。

送信先だけど、このメソッドClassDescriptionにあり、どうやらインスタンスメソッドを追加するみたいだ。Fooクラスにクラスメソッドを追加する場合は、Foo classクラスに対して送信すればいい。

インスタンスメソッドを作る場合はFooクラスに送信すればいいんだけど、メニューからはメタクラスに送られること、Polymorphicでより意味的に自然な気がするので、Foo class側からself soleInstanceに送ることにした。

というわけで次のようなコードをClassDescriptionMetaclassに追加して完成。

ClassDescription >> copyClassOrInstMethodTarget
    ^self class

ClassDescription >> copyClassOrInstMethod: aMessage
    |method|
    method := self compiledMethodAt: aMessage.
    self copyClassOrInstMethodTarget
        addAndClassifySelector: aMessage
        withMethod: method
        inProtocol: method protocol
        notifying: nil.

ClassDescription >> toggleClassOrInstMethod: aMessage
    self
        copyClassOrInstMethod: aMessage;
        removeSelector: aMessage.

Metaclass >> copyClassOrInstMethodTarget
    ^self soleInstance

感想

デバッガの味を覚えた、俺はもうだめだ

広告を非表示にする