リフレクション

要約:
  • 実行時に Curl プログラムの構造をインスペクトして使用します。

概要

Curl® リフレクション インターフェイスをプログラムで使用すると、Curl プログラムを実行時にインスペクトして使用するデータとして扱うことができます。これは Curl パッケージの public 属性の機能へのアクセスを提供し、現時点では、privatepackage 機能にはリフレクション インターフェイスを使ってアクセスできません。プログラムでは、デバックの際にリフレクションを使用したり、コンパイル時に不明のコードに対し、対話型あるいはデータドリブンのインターフェイスを提供する目的でこれを使用できます。
注意: リフレクション インターフェイスを使い、このインターフェイスについて学ぶこともできます。

パッケージと PackageMembers

リフレクション インターフェイスは、Curl プログラムを Package の下にネストされる名前空間のツリーとして表します。「パッケージ」を参照してください。たとえば、get-current-package は実行されるプログラムのパッケージを返します。パッケージは他のパッケージをインポートでき、これは Package.imported-packages で表されます。

例: Package およびインポートされたパッケージへのアクセス
{value
    let current:VBox = {VBox}
    {current.add
        {HBox
            |"{get-current-package} : "|,
            {String {get-current-package}}
        }
    }
    let imports:VBox = {VBox}
    {for p in {get-current-package}.imported-packages do
        {imports.add {String p}}
    }
    {current.add
        {HBox
            |"{get-current-package}.imported-packages : "|,
            imports
        }
    }
    current
}
プログラムでは、ComponentSelector を使ってパッケージを名前で参照することもできます。パッケージには直接 PackageMember オブジェクトが含まれていますが、これらは Package.get-members を使って返すことができます。

例: パッケージを名前で選択
{value
    let p:Package =
        {import-package
            {ComponentSelector name = "CURL.LANGUAGE.REFLECTION"}
        }
    let members:VBox = {VBox}
    {for m in {p.get-members} do
        {members.add {String m}}
    }
    {VBox
        {HBox
            {String p & ".get-members : "},
            members
        }
    }
}
パッケージ メンバには、定数、変数、プロシージャ、クラスがあります。当然のことですが、PackageMember クラス自体も CURL.LANGUAGE.REFLECTION パッケージのメンバです。その他のパッケージ メンバは Curl パッケージの他の機能を特定して表現するクラスで、これらについては後半で詳細に説明します。

PackageMember クラス

Package.get-members は、パッケージの各 public 機能の PackageMember オブジェクトを返します。プログラムでは、Package.get-member を呼び出し、任意の public 機能の名前を指定してその PackageMember オブジェクトを返すこともできます。PackageMember は、そのメンバを表すアクセッサ、値を取得および設定するメソッドをエクスポートします。次の例では、CURL.LANGUAGE.REFLECTION パッケージの PackageMember メンバにアクセスしています。PackageMember はクラスであり、そのクラス自体もリフレクションパッケージの PackageMember です。

例: 名前で PackageMember を選択
{import * from CURL.LANGUAGE.REFLECTION}

{value
    let p:Package =
        {import-package
            {ComponentSelector name = "CURL.LANGUAGE.REFLECTION"}
        }
    let m:#PackageMember =
        {p.get-member "PackageMember", check-imports? = false}
    {table
        {row
            {cell {String m & ".access"}}
            {cell {String m.access}}
        }
        {row
            {cell {String m & ".constant?"}}
            {cell {String m.constant?}}
        }
        {row
            {cell {String m & ".name"}}
            {cell {String m.name}}
        }
        {row
            {cell {String m & ".package"}}
            {cell {String m.package}}
        }
        {row
            {cell {String m & ".public?"}}
            {cell {String m.public?}}
        }
        {row
            {cell {String m & ".type"}}
            {cell {String m.type}}
        }
    }
}
CURL.LANGUAGE.REFLECTION パッケージにはクラスしか含まれていませんが、パッケージには変数やプロシージャも含めることができます。たとえば、CURL.RUNTIME.PROCESS パッケージで get-current-package プロシージャを探し、このプロパティの一部をダンプしてみましょう。

例: PackageMember の使用
{import * from CURL.LANGUAGE.REFLECTION}

{value
    let p:Package =
        {import-package
            {ComponentSelector name = "CURL.RUNTIME.PROCESS"}
        }
    let m:#PackageMember = {p.get-member "get-current-package"}
    {table
        {row
            {cell {String m & ".access"}}
            {cell {String m.access}}
        }
        {row
            {cell {String m & ".constant?"}}
            {cell {String m.constant?}}
        }
        {row
            {cell {String m & ".name"}}
            {cell {String m.name}}
        }
        {row
            {cell {String m & ".package"}}
            {cell {String m.package}}
        }
        {row
            {cell {String m & ".public?"}}
            {cell {String m.public?}}
        }
        {row
            {cell {String m & ".type"}}
            {cell {String m.type}}
        }
    }
}
次に、get-current-package が適切なオブジェクトを返すというアサーション文の後で、PackageMember を使ってこのプロシージャを呼び出します。

例: PackageMember 使ってプロシージャを呼び出す
{import * from CURL.LANGUAGE.REFLECTION}

{value
    let p:Package =
        {import-package
            {ComponentSelector name = "CURL.RUNTIME.PROCESS"}
        }
    let m:#PackageMember = {p.get-member "get-current-package"}
    {assert m.type == {type-of get-current-package}}
    {assert {m.get-value} == get-current-package}
    {assert {{m.get-value}} == {get-current-package}}
    {table
        {row
            {cell direct}
            {cell reflection}
        }
        {row
            {cell get-current-package}
            {cell {String m.name}}
        }
        {row
            {cell {type-of get-current-package}}
            {cell {String m.type}}
        }
        {row
            {cell {value get-current-package}}
            {cell {m.get-value}}
        }
        {row
            {cell {get-current-package}}
            {cell {{m.get-value}}}
        }
    }
}
同様に、PackageMember.constant?false の場合にパブリック メンバの値を設定できる PackageMember.set-value メソッドがあります。

TypeProcType および ClassType

上記の {type-of get-current-package} の値は、ProcType の一例です。Curl 言語には、名前なしリテラル (つまり数字や文字列などのような) として ProcType を作成する proc-type 構文があります。クラスなどの他の Curl 型はその名前で識別されます。TypeProcType および ClassType クラスは CURL.LANGUAGE.REFLECTION パッケージに先行するものであり、上記の get-members で返される PackageMember オブジェクトのリストには表示されません。
TypeProcType および ClassType クラスは非常に大きなクラスなので、このページでは詳細な解説は提供していませんが、これらはすべて Curl のリフレクション機能を増補するものです。たとえば、プログラムで ClassType.direct-superclasses を使い、そのクラスの継承図を参照できます。さらに ClassType には多様なクラス機能をその名前で参照できる多くのメソッドが含まれています。より一般的な ClassType.get-members メソッドについては下記で詳細に解説しています。ProcType についても、プロシージャのように呼び出せるクラス機能に関与する範囲内で説明しています。

クラスと ClassMember

クラスは、PackageMember オブジェクトで表現される Package 名前空間内に位置するもう 1 つの名前空間層です。クラス メンバには、クラス定数、変数、プロシージャ、フィールド、ゲッター/セッター、メソッド、コンストラクタとファクトリー、オプションがあります。ClassType は Curl クラスを表現するもので、クラスの public 機能にアクセスするメソッドと、クラスでアクセス可能な各メンバの ClassMember オブジェクトを返す ClassType.get-members をエクスポートします。リフレクションを使って ClassMember クラスのメンバを調べてみましょう。

例: ClassMember のクラス メンバを調べる
{import * from CURL.LANGUAGE.REFLECTION}

{value
    let t:ClassType = ClassMember
    let members:Table =
        {Table
            {row-prototype
                {cell-prototype t}
            },
            {row-prototype
                {cell-prototype "member"},
                {cell-prototype "name"}
            }
        }
    {for m in {t.get-members search-superclasses? = false} do
        {members.add
            {row-prototype
                {cell-prototype m},
                {cell-prototype m.name}
            }
        }
    }
    members
}
ClassType.get-members によって返されるすべてのメンバでは、ClassMember.public?true である点に注意してください。これは、現時点では Curl リフレクションが public 属性のプログラム機能しかエクスポートしないためです。
名前に -filter を含むクラス プロシージャは、クラスで特定の種類の機能を探し出すという便利な方法を提供します。たとえば、次のコードでは Table クラスで直接実装されているオプションを検索しています。ClassMember.option-filterOption クラス メンバの場合には true を返し、その他のメンバでは false を返します。

例: Table のオプションを調べる
{import * from CURL.LANGUAGE.REFLECTION}

{value
    let t:ClassType = Table
    let members:Table =
        {Table
            {row-prototype
                {cell-prototype "member"},
                {cell-prototype "name"},
                {cell-prototype "type"}
            }
        }
    {for m in {t.get-members
                  search-superclasses? = false,
                  filter = ClassMember.option-filter
              }
     do
        {members.add
            {row-prototype
                {cell-prototype m},
                {cell-prototype m.name},
                {cell-prototype m.type}
            }
        }
    }
    members
}
ClassMember.get-all-filter は、すべての ClassMember オブジェクトに対して true を返すことになります。

クラス変数

クラス変数はその名前にクラス名を付けてアクセスする変数で、実質上グローバルであり、特定のオブジェクト だけに存在するものではありません。

例: ClassVariable のクラス メンバを調べる
{import * from CURL.LANGUAGE.REFLECTION}

{value
    let t:ClassType = ClassVariable
    let members:Table =
        {Table
            {row-prototype
                {cell-prototype t}
            },
            {row-prototype
                {cell-prototype "member"},
                {cell-prototype "name"},
                {cell-prototype "type"}
            }
        }
    {for m in {t.get-members search-superclasses? = false} do
        {members.add
            {row-prototype
                {cell-prototype m},
                {cell-prototype m.name},
                {cell-prototype m.type}
            }
        }
    }
    members
}
リフレクションを使ってクラス変数の値を取得および設定する方法を次に示します。
構文 :
let v = {cv.get-value}
{cv.set-value v}
説明 :
cvClassVariable
vcv.type 型の値。

プロパティ

クラス内のすべてのオプション、フィールド、ゲッター、セッターのコレクションはプロパティ と呼ばれます。Curl 言語では、プロパティ値の取得および設定に共通の構文を使います。「プロパティ」を参照してください。この共通性は、実行時に Property 抽象クラスに反映されます。PropertyClassMember を継承します。上記の例でも見たように、継承した機能が検索されないようにするには search-superclasses? = falseClassType.get-members に渡します。

例: Property のクラス メンバを調べる
{import * from CURL.LANGUAGE.REFLECTION}

{value
    let t:ClassType = Property
    let members:Table =
        {Table
            {row-prototype
                {cell-prototype t}
            },
            {row-prototype
                {cell-prototype "member"},
                {cell-prototype "name"},
                {cell-prototype "type"}
            }
        }
    {for m in {t.get-members search-superclasses? = false} do
        {members.add
            {row-prototype
                {cell-prototype m},
                {cell-prototype m.name},
                {cell-prototype m.type}
            }
        }
    }
    members
}
GetterOptionField および Setter クラスはすべて Property を継承します。ClassVariable と同様に、Property クラスには get-valueset-value メソッドがありますが、これらには操作の対象となるオブジェクトも指定します。Curl リフレクション パッケージがエクスポートする実行時のインターフェイスは、プロパティ値をその名前で取得および設定する場合の言語構文に対応します。
構文 :
let v = {p.get-value obj}
{p.set-value obj, v}
説明 :
pobjProperty
objp.declaring-class 型のオブジェクト。
vobjpp.type 型の値。

コンストラクタとファクトリー

ConstructorFactory クラスは InstanceMaker を継承します。InstanceMaker は、新しい Curl クラス オブジェクトを作成できる new メンバを持つ ClassMember です。InstanceMaker.typeProcType で、プログラムではこれを使って引数を調べ、コンストラクタまたはファクトリーが必要とする型を返すことができます。

例: リフレクションを使ったオブジェクト作成
{import * from CURL.LANGUAGE.REFLECTION}

{value
    let t:ClassType = String
    let members:Table =
        {Table
            {row-prototype
                {cell-prototype t}
            },
            {row-prototype
                {cell-prototype "member"},
                {cell-prototype "name"},
                {cell-prototype "type"},
                {cell-prototype |"{f.new 'X', 3}"|},
                {cell-prototype |"{type-of {f.new 'X', 3}}"|}
            }
        }
    {for m in {t.get-members
                  search-superclasses? = false,
                  filter = ClassMember.instance-maker-filter
              } do
        let f:Factory = m asa Factory
        {members.add
            {row-prototype
                {cell-prototype f},
                {cell-prototype f.name},
                {cell-prototype f.type},
                {cell-prototype {f.new 'X', 3}},
                {cell-prototype {type-of {f.new 'X', 3}}}
            }
        }
    }
    members
}

メソッド

Method は、Curl クラス オブジェクトに対して呼び出すことができる ClassMember です。 ClassType.get-methodMethod を返します。 Method クラスには 2 つのメソッド、Method.invokeMethod.invoke-n があり、プログラムではこれらを使って実行時に表現されるメソッドを呼び出すことができます。2 つのメソッドの相違点は、その結果の返し方にあります。ここでは Method クラス自体に対してリフレクションを使い、これらのメソッドとその型を調べてみましょう。

例: Method のメソッドを調べる
{import * from CURL.LANGUAGE.REFLECTION}

{value
    let t:ClassType = Method
    let members:Table =
        {Table
            {row-prototype
                {cell-prototype t}
            },
            {row-prototype
                {cell-prototype "member"},
                {cell-prototype "name"},
                {cell-prototype "type"}
            }
        }
    {for m in {t.get-members
                  search-superclasses? = false,
                  filter = ClassMember.method-filter
              } do
        {members.add
            {row-prototype
                {cell-prototype m},
                {cell-prototype m.name},
                {cell-prototype m.type}
            }
        }
    }
    members
}
Method.typeProcType で、メソッドが表現する引数と戻り型を返します。Method.invoke は、複数の戻り値に対し Curl の構文で直接使用できる結果を最高 4 つまで返します。Method.invoke-n は、任意数の結果を単一の {FastArray-of any} として返し、呼び出し側はこれを繰り返し処理することができます。メソッドをその Method オブジェクトを介して実際に呼び出すには、メソッドを呼び出すターゲット オブジェクトを指定しなければならないことから、かなり複雑であるといえます。Method.invokeMethod.invoke-n メソッドの ProcType ではこのターゲットは暗黙的に指定され、慣習により最初の引数になります。つまり、次のようになります。
構文 :
直接 : let v = {o.f ...}
リフレクション : let v = {m.invoke-n o, ...}
let a = {m.invoke o, ...}
説明 :
oクラス C のオブジェクト。
fC のメソッド。
mメソッド C.f を表す Method オブジェクト。
つまり、m{t.get-method "f"} の値で、t(C asa ClassType) になります。
...C.f メソッドの引数。
上記の説明を次の例で実際に使ってみましょう。

例: リフレクションを使ったメソッド呼び出し
{import * from CURL.LANGUAGE.REFLECTION}

{table
    {row
        {cell direct:}
        {cell
            {{String 'X', 3}.equal?
                {String.repeat-char 'x', 3},
                ignore-case? = true
            }
        }
    }
    {row
        {cell reflection:}
        {cell
            {{(String asa ClassType).get-method "equal?"}.invoke
                {{(String asa ClassType).get-instance-maker "default"}.new
                    'X', 3
                },
                {{(String asa ClassType).get-instance-maker "repeat-char"}.new
                    'x', 3
                },
                ignore-case? = true
            }
        }
    }
}