関数オブジェクトから処理内容を取得して書き換える―を Squeak Smalltalk で
ただの言語には興味ありません。この中に「関数オブジェクトから関数の処理内容を取得して書き換えられる言語」「グローバル変数とクロージャを使わずに、呼ぶ度に動作が変わる関数を作れる言語」「標準入出力へのIOが標準では存在しない言語」があったら僕に報告しなさい、以上。
Twitter / tana
実に面白い。やってみよう。
趣旨をはずしているかもしれないけれど(たぶんはずしているだろうけど^^;)、きにしない。
■関数オブジェクトから関数の処理内容を取得して書き換える
Squeak Smalltalk では、クラスに「>> #メソッド名」(あるいは「compiledMethodAt: #メソッド名」)というメッセージをを送信することで、関数メソッドならぬ「メソッドオブジェクト」を得ることができます。
fact := Integer >> #factorial "=> a CompiledMethod (3603) "
ちなみにメソッドオブジェクトを静的にコールするには #valueWithReceiver:arguments: を使います。
10 factorial "=> 3628800 " "通常の動的コール" fact valueWithReceiver: 10 arguments: #() "=> 3628800 " "静的コール"
このメソッドオブジェクトに対しさらに getSource を送信することで Smalltalk コードのかたちで、あるいは symbolic を送信することでバイトコードプログラムとして、その処理の内容を知ることができます。
fact getSource asString
=> factorial "Answer the factorial of the receiver." self = 0 ifTrue: [^ 1]. self > 0 ifTrue: [^ self * (self - 1) factorial]. self error: 'Not valid for negative integers'
fact symbolic
=> 25 <70> self 26 <75> pushConstant: 0 27 <B6> send: = 28 <99> jumpFalse: 31 29 <76> pushConstant: 1 30 <7C> returnTop 31 <70> self 32 <75> pushConstant: 0 33 <B3> send: > 34 <9E> jumpFalse: 42 35 <70> self 36 <70> self 37 <76> pushConstant: 1 38 <B1> send: - 39 <D0> send: factorial 40 <B8> send: * 41 <7C> returnTop 42 <70> self 43 <22> pushConstant: 'Not valid for negative integers' 44 <E1> send: error: 45 <87> pop 46 <78> returnSelf
さらにメソッドオブジェクトの実体は単なるバイト列なので、直接いじるのもよし―
fact byteAt: 37 put: 16r77. "1を減ずるところを2を減じるように変更" fact decompile
=>
factorial
self = 0
ifTrue: [^ 1].
self > 0
ifTrue: [^ self * (self - 2) factorial].
self error: 'Not valid for negative integers'
10 factorial "=> 3840 " fact valueWithReceiver: 10 arguments: #() "=> 3840 "
fact byteAt: 37 put: 16r76 "元に戻す"
10 factorial "=> 3628800 " fact valueWithReceiver: 10 arguments: #() "=> 3628800 "
(通常のメソッド再定義との意味の違いをほとんど見いだせませんが―)メソッドオブジェクトから元となったノードを得て、それに修正を加えて再コンパイル後、#become: ですげ替えるのもよし―
node := fact methodNode. expr := node block statements second arguments first statements first expr. encoder := Encoder new init: Integer context: nil notifying: nil. expr selector: (encoder encodeSelector: #+). "乗算を加算に変更" fact become: (alternative := node generate)
10 factorial "=> 56 " fact valueWithReceiver: 10 arguments: #() "=> 56 "
fact become: alternative "元に戻す"
10 factorial "=> 3628800 " fact valueWithReceiver: 10 arguments: #() "=> 3628800 "
まったく別の処理をコンパイルして同様にすげ替えるのもよし―
alternative := (Parser new parse: 'factorial ^self' class: Integer) generate. fact become: alternative.
10 factorial "=> 10 " fact valueWithReceiver: 10 arguments: #() "=> 10 "
fact become: alternative "元に戻す"
10 factorial "=> 3628800 " fact valueWithReceiver: 10 arguments: #() "=> 3628800 "
と、お好きなアプローチで処理を書き換えることが可能です。
なお、Smalltalk で関数オブジェクトと言うと他にも「ブロック」という無名関数オブジェクトがありますが、その実体もじつはメソッドなので原則としてこれと同じことが(もうちょっとややこしくはなりそうですが―)できるはずです。試してませんけど。^^;
■呼ぶ度に動作が変わる関数を作る
厳密には「呼ぶ度に動作が変わる」という“一定の”動作を記述しているだけなので、題意からは外れているかもしれませんが,呼ぶ度に処理内容が変わるという解釈で、自己を書き換えて呼ぶ度に動作を変える関数を記述してみます。
UndefinedObject >> flipFlop | method | method := thisContext method. method byteAt: 49 put: ((method byteAt: 49) bitXor: 3). ^false
nil flipFlop "=> true " nil flipFlop "=> false " nil flipFlop "=> true " nil flipFlop "=> false " nil flipFlop "=> true " ...