引き続き、日経 Linux 10 月号、「プログラミングのオキテ」にある Ruby のリフレクション機能の表を参考に、Squeak システムの Smalltalk との対応表を作ってみました。
リフレクション機能 | Ruby | Squeak の Smalltalk |
---|---|---|
メソッド名の一覧 | Foo.instance_methods | Foo allSelectors |
メソッド名の一覧 (継承分を除く) |
Foo.instance_methods(false) | Foo selectors |
オブジェクトのメソッド名の一覧 | obj.methods | obj class allSelectors |
特異メソッド名の一覧 | obj.singleton_methods | " n/a " |
特異メソッド名の一覧 (継承分を除く) |
obj.singleton_methods(false) | " n/a " |
インスタンス変数名の一覧 | obj.instance_variables | Foo allInstVarNames |
インスタンス変数名の一覧 (継承分を除く) |
#n/a | Foo instVarNames |
インスタンス変数名の一覧 (継承関係にあるもの全て) |
#n/a | Foo allInstVarNamesEverywhere |
サブクラスのインスタンス変数名の一覧 | #n/a | Foo subclassInstVarNames |
オブジェクトのインスタンス変数名の一覧 | obj.instance_variables | obj class allInstVarNames |
インスタンス変数値の取得 | obj.instance_variable_get(:@var) | obj instVarNamed: #var |
インスタンス変数値の設定 | obj.instance_variable_set(:@var, "value") | obj instVarNamed: #var put: 'value' |
インスタンス変数の追加 | obj.instance_variable_set(:@var, "value") | Foo addInstVarName: #var |
インスタンス変数の削除 | obj.instance_eval{remove_instance_variable(:@var)} obj.__send__(:remove_instance_variable,:@var) |
Foo removeInstVarName: #var |
インスタンス変数の定義クラス | #n/a | Foo classThatDefinesInstanceVariable: #var |
非参照のインスタンス変数名の一覧 | #n/a | Foo allUnreferencedInstanceVariables |
非参照のインスタンス変数名の一覧 (継承分を除く) |
#n/a | Foo unreferencedInstanceVariables |
グローバル変数名の一覧 | global_variables | Smalltalk keys |
ローカル変数名の一覧 | local_variables | thisContext tempNames |
クラス(モジュール)定数名の一覧 | Foo.constants | Smalltalk associations select: [:assoc| assoc isKindOf: ReadOnlyVariableBinding] |
定数値の取得 | Foo.const_get(:FOO) | Smalltalk at: #foo |
定数値の設定 | Foo.const_set(:FOO, "value") | (Smalltalk associationAt: #foo) privateSetKey: #foo value: 'value' |
定数の削除 | Foo.class_eval{remove_const(:FOO)} Foo.__send__(:remove_const,:FOO) |
Smalltalk removeAt: #foo |
クラス変数名の一覧 | Foo.class_variables | Foo allClassVarNames |
クラス変数名の一覧 (継承分を除く) |
#n/a | Foo classVarNames |
クラス変数値の取得 | Foo.class_eval{class_variable_get(:@@var)} Foo.__send__(:class_variable_get,:@@var) |
Foo classPool at: #Var |
クラス変数値の設定 | Foo.class_eval{class_variable_set(:@@var,"value")} Foo.__send__(:class_variable_set,:@@var,"value") |
Foo classPool at: #Var put: 'value' |
クラス変数の追加 | Foo.class_eval{class_variable_set(:@@var,"value")} Foo.__send__(:class_variable_set,:@@var,"value")} |
Foo addClassVarName: #Var |
クラス変数の削除 | Foo.class_eval{remove_class_variable(:@@var)} Foo.__send__(:remove_class_variable,:@@var) |
Foo removeClassVarName: #Var |
クラス変数の定義クラス | #n/a | Foo classThatDefinesClassVariable: #Var |
非参照のクラス変数名の一覧 | #n/a | Foo allUnreferencedClassVariables |
プール変数辞書の一覧 | #n/a | Foo allSharedPools |
プール変数辞書の一覧 (継承分を除く) |
#n/a | Foo sharedPools |
プール変数辞書の追加 | #n/a | Foo addSharedPool: #VarDict |
プール変数辞書の削除 | #n/a | Foo removeSharedPool: #VarDict |
メソッドを定義 | Foo.class_eval{define_method(:foo){"foo"}} Foo.__send__(:define_method,:foo,proc{"foo"}) |
Foo compile: 'foo ^#foo' classified: ClassOrganizer default |
メソッドを削除 | Foo.class_eval{remove_method(:foo)} Foo.__send__(:remove_method,:foo) |
Foo removeSelector: #foo |
メソッドを未定義化 | Foo.class_eval{undef_method(:foo)} Foo.__send__(:undef_method,:foo) |
" n/a " |
メソッドに別名を付与 | Foo.class_eval{alias_method(:bar,:foo)} Foo.__send__(:alias_method,:bar,:foo) |
"別名のメソッドを定義" |
指定メソッドの定義場所の一覧 | #n/a | SystemNavigation default allImplementorsOf: #foo |
指定メソッドの定義場所の一覧 (指定した継承ツリー内限定) |
#n/a | SystemNavigation default allImplementorsOf: #foo localTo: Foo |
指定メソッドのコール場所の一覧 | #n/a | SystemNavigation default allCallsOn: #foo |
指定メソッドのコール場所の一覧 (指定した継承ツリー内限定) |
#n/a | SystemNavigation default allCallsOn: #foo from: Foo |
メソッドカテゴリとメソッド名の一覧 | #n/a | Foo organization |
メソッドカテゴリ内のメソッド名の一覧 | #n/a | Foo allMethodsInCategory: 'accessing' |
メソッドが属するカテゴリの取得 | #n/a | Foo whichCategoryIncludesSelector: #foo |
メソッドカテゴリとメソッドの削除 | #n/a | Foo removeCategory: 'as yet unclassified' |
(コンパイル済み)メソッドの取得 | Foo.new.method(:foo) | Foo >> #foo |
メソッドの定義者とその日時 | #n/a | (Foo >> #foo) timeStamp |
メソッドの最初のコメント | #n/a | Foo firstCommentAt: #foo |
メソッドのセレクタ | Foo.new.method(:foo).inspect | (Foo >> #foo) selector |
メソッドの引数の数 | Foo.new.method(:foo).arity | (Foo >> #foo) numArgs |
メソッド定義内のローカル変数の数 | #n/a | (Foo >> #foo) numTemps |
メソッド定義内のリテラルの一覧 | #n/a | (Foo >> #foo) literals |
メソッド定義内のセレクタの一覧 | #n/a | (Foo >> #foo) messages |
メソッド定義のソース | #n/a | (Foo >> #foo) getSourceFromFile |
メソッド定義のソース(デコンパイル) | #n/a | (Foo >> #foo) decompileString |
簡易注釈付き バイトコード出力 |
#n/a | (Foo >> #foo) symbolic |
モジュールをインクルード | Foo.class_eval{include(Math)} Foo.__send__(:include,Math) |
" n/a " |
インクルードされている モジュールを取得 |
Foo.included_modules | " n/a " |
スーパークラスを取得 | Foo.superclass | Foo superclass |
スーパークラスを設定 | #n/a | Foo superclass: NewSuperOrNil |
スーパークラス (モジュール)名の一覧 |
Foo.ancestors | Foo allSuperclasses |
サブクラスの一覧 | #n/a | Foo subclasses |
サブクラスの一覧(孫も) | #n/a | Foo allSubclasses |
クラスカテゴリの取得 | #n/a | Foo category |
クラスカテゴリの設定 | #n/a | Foo category: 'Category-Name' |
クラスコメントの取得 | #n/a | Foo comment |
クラスコメントの設定 | #n/a | Foo comment: 'I am a sacrifice to you.' |
インスタンスの一覧 | a=[];ObjectSpace.each_object(Foo){ |obj|a<<obj if obj.class==Foo};a |
Foo allInstances |
インスタンスの総数 | a=[];ObjectSpace.each_object(Foo){ |obj|a<<obj if obj.class==Foo};a.length |
Foo instanceCount |
インスタンスの一覧 (サブクラス込み) |
a=[];ObjectSpace.each_object(Foo){ |obj|a<<obj};a |
Foo allSubInstances |
インスタンスの総数(サブクラス込み) | ObjectSpace.each_object(Foo){} | Foo allSubInstances size |
継承をフック | inherited を定義 | #subclass:instanceVariableNames: classVariableNames:poolDictionaries: category: をメタクラスに再定義 |
インクルードをフック | included を定義 | " n/a " |
メソッドの定義をフック | method_added を定義 | #compile:classified:withStamp: notifying:logSource: をメタクラスに再定義 |
メソッドの削除をフック | method_removed を定義 | #basicRemoveSelector: をメタクラスに再定義 |
特異メソッド定義をフック | singleton_method_added を定義 | " n/a " |
特異メソッドの削除をフック | singleton_method_removed を定義 | " n/a " |
特異メソッドの未定義化をフック | singleton_method_undefined を定義 | " n/a " |
未定義メソッドの起動をフック | method_missing を再定義 | #doesNotUnderstand: を再定義 |
文字列を評価 | eval("3+4") | Compiler evaluate: '3+4' |
オブジェクトの文脈で 文字列を評価 |
3.instance_eval("self+4") | Compiler evaluate: 'self+4' for: 3 logged: false |
クラスの文脈で 文字列を評価 |
Foo.class_eval("name") | Compiler evaluate: 'self name' for: Foo logged: false |
式が「定義されている」かどうかを判定 | defined? | " n/a " |
定数
Smalltalk では、変更の必要のない定数は通常、メソッドとして定義します。が、最近の Squeak システムでは、グローバル変数を拡張した定数っぽい機能(参照専用のグローバル変数)も使えるようになっています。ただ、Ruby の定数と異なり再代入はできません(警告ではなくエラーになります)。Smalltalk at: #foo put: 10 " グローバル変数の定義 "
foo " => 10 " foo := 20 foo " => 20 "
(Smalltalk associationAt: #foo) beReadOnlyBinding " グローバル変数を定数に "
foo := 30 " => Error: Cannot store into read-only bindings "
特異メソッド
Smalltalk には Ruby の特異メソッド(インスタンス特異的メソッド)のようなものはないのですが、Squeak システムには #become: (id:sumim:20050823:p2) を利用した #assureUniClass というメソッドがあるので、これを用いることで似たようなことは可能です。| ordinary sole | ordinary := 'string'. sole := ordinary copy. sole assureUniClass. " 特異クラス様クラスを動的に定義してそのインスタンスに #become: " sole class compile: 'value ^''instance specific...''' classified: 'evaluating'. ^ {{sole reverse. ordinary reverse}. {sole value. ordinary value}}
=> #(#('gnirts' 'gnirts') #('instance specific...' 'string'))Object >> #value はレシーバをそのまま返します。 コメントにもあるように、Object >> #assureUniClass は、レシーバのクラスの名前に連番を振ったクラスを動的に作成したうえで、さらにそのインスタンスを(レシーバに似せた状態で)生成してレシーバに #become: します。ここで動的に作成されるクラスへの働きかけのほとんどをスーパークラス(#assureUniClass する前のクラス)に委譲してしまうような仕組みを設けることができれば、Ruby の特異クラスとほぼ同様のカラクリ(ひいては特異メソッド)も実現可能でしょう。