パラメトリック型は、パラメータを取得できる型です。 Verse でパラメトリック型を使用して、汎用的なデータ構造と操作を定義できます。 パラメトリック型は、関数の明示的または暗黙的な型引数として使用する方法と、クラスの明示的な型引数として使用する方法の 2 つがあります。
イベントはパラメトリック型の一般的な例であり、UEFN の仕掛け全体で広く使用されています。 たとえば、ボタンの仕掛けに、プレイヤーがこのボタンを操作するたびに発生する InteractedWithEvent があるとします。 アクション内でパラメトリック型を確認するには、カスタム仕様のカウントダウン タイマー チュートリアルの CountdownEndedEvent を参照してください。
明示的な型引数
2 つの引数を取得する box を考えてみます。 first_item は ItemOne を初期化し、second_item は ItemTwo を初期化します。どちらの型も type です。 first_item と second_item のどちらも、クラスの明示的な引数となるパラメトリック型の例です。
box(first_item:type, second_item:type) := class:
ItemOne:first_item
ItemTwo:second_itemtype は first_item と second_item の型引数であるため、この 2 つの型のどちらでも box クラスを作成することができます。 2 つの string 値を持つボックスや、2 つの int 値を持つボックス、string と int、または 2 つのボックスから成るボックスも作ることができます。
次の例として MakeOption() 関数を考えてみます。これは任意の型を取得して、その型の option を返します。
MakeOption(t:type):?t = false
IntOption := MakeOption(int)
FloatOption := MakeOption(float)
StringOption := MakeOption(string)代わりに array や map など、他のコンテナ型を返すように MakeOption() 関数を変更することもできます。
暗黙的な型引数
関数の暗黙的な型引数は where キーワードを使用して導入されます。 たとえば、パラメータを取得してそれを返すだけの関数 ReturnItem() の場合は次のようになります。
ReturnItem(Item:t where t:type):t = Itemここで、t は関数 ReturnItem() の暗黙的な型パラメータです。これは型 type の引数を取得すると、すぐにそれを返します。 t の型により、この関数に渡すことができる Item の型が制限されます。 この場合、t は型が type であるため、任意の型で ReturnItem() を呼び出すことができます。 関数で暗黙的なパラメトリック型を使用するのは、渡す型に関係なく機能するコードを記述することができるためです。
たとえば、次のように記述する必要はありません。
ReturnInt(Item:int):int = Item
ReturnFloat(Item:float):float = Item代わりに、1 つの関数を記述することができます。
ReturnItem(Item:t where t:type):t = Itemこれにより、ReturnItem() が t の特定の型を知る必要がないということが保証されています。それが実行する操作が何であろうと、t の型に関係なく機能します。
t に使用する実際の型は、ReturnItem() の使用方法によって異なります。 たとえば、ReturnItem() が引数「0.0」で呼び出された場合、t は float です。
ReturnItem("t") # t is a string
ReturnItem(0.0) # t is a floatここで、"hello" と 0.0 は ReturnItem() に渡される明示的な引数 (Item) です。 暗黙的な型の Item は t であるため、任意の type で構わないため、これらはどちらも機能します。
関数への暗黙的な引数になるパラメトリック型の別の例として、box クラスで動作する次のような MakeBox() 関数について考えます。
box(first_item:type, second_item:type) := class:
ItemOne:first_item
ItemTwo:second_item
MakeBox(ItemOneVal:ValOne, SecondItemVal:ValTwo where ValOne:type, ValTwo:type):box(ValOne, ValTwo) =
box(ValOne, ValTwo){ItemOne := ItemOneVal, ItemTwo := SecondItemVal}
Main():void =
MakeBox("A", "B")
MakeBox(1, "B")
ここで、MakeBox() 関数は FirstItemVal と SecondItemVal の 2 つの引数を取得しますが、これはどちらも型が type で、型 (type、type) のボックスを返します。 ここで type の使用は、返されるボックスが任意の 2 つのオブジェクト (配列、文字列、関数など) で構成されている可能性を MakeBox に伝えていることを意味します。 MakeBox() 関数は両方の引数を Box に渡し、それらを使用してボックスを作成して、それを返します。 box と MakeBox() のどちらも、同じ構文を関数呼び出しとして使用することに注意してください。
次に示すビルトインのサンプルは、Map コンテナ型の関数です。
Map(F(:t) : u, X : []t) : []u =
for (Y : X):
F(Y)型の制約
式の型に対する制約を指定できます。 現在サポートされている制約はサブタイプのみとなり、暗黙的な型パラメータのみを対象としています。 例:
int_box := class:
Item:int
MakeSubclassOfIntBox(NewBox:subtype_box where subtype_box:(subtype(int_box))) : tuple(subtype_box, int) = (NewBox, NewBox.Item)この例では、SubtypeBox の型が (subtype(IntBox)) であるため、MakeSubclassOfIntBox() は IntBox からサブクラス化したクラスが渡されたときにのみコンパイルします。 type は、subtype(any) の省略表現として見られることがある点に注意してください。 つまり、この関数は any のサブタイプであればすべて受け取りますが、これはすべての型を示しています。
共変性と反変性
共変性と反変性は、型が複合型または関数で使用される場合の 2 つの型の関係性を示します。 一方がもう一方のサブクラスである場合など、何らかの関係がある 2 つの型は、特定のコードで使用される方法に応じてお互いに共変や反変になります。
共変: コードでより汎用的な型が想定されている場合に、より具体的な型を使用します。
反変: コードでより具体的な型が想定されている場合に、より汎用的な型を使用します。
たとえば、int を任意の comparable が受け入れられる場合に使用すると (float など)、int は共変的に作用します。これは、より汎用的なものを期待しているときに、より具体的な型を使用しているためです。 反対に、通常は int が使用される場合に任意の comparable を使用すると、comparable は反変的に作用します。これは、より具体的なものを期待しているときに、より汎用的な型を使用しているためです。
パラメトリック型における共変性と反変性の例は、次のようになります。
MyFunction(Input:t where t:type):logic = trueここで、t は関数への入力として反変的に使用され、logic は関数への出力として共変的に使用されています。
2 つの型は本質的に、お互いに共変または反変であるわけではなく、コード内で使用されている方法によって共変または反変として作用することに注意する必要があります。
共変性
共変性とは、汎用的なものが期待されるときに、より具体的なものを使用することです。 通常、これは関数の出力用です。 関数への入力ではない型の使用はすべて、共変的な使用です。
次に示す汎用的なパラメトリック型の例では、payload が共変的に作用しています。
DoSomething():int =
payload:int = 0たとえば、クラス animal と、animal のサブクラスである cat があるとします。 さらに、関数 AdoptPet() を使用してペットを里子に出す動物保護施設を示すクラス pet_sanctuary もあります。 これからどのようなペットを飼うかわからないため、AdoptPet() は汎用的な animal を返します。
animal := class:
cat := class(animal):
pet_sanctuary := class:
AdoptPet():animal = animal{}今度は別の、ネコ専用の保護施設があるとします。 このクラス cat_sanctuary は pet_sanctuary のサブクラスです。 これはネコの保護施設のため、animal の代わりに cat しか返さないように AdoptPet() をオーバーライドします。
cat_sanctuary := class(pet_sanctuary):
AdoptPet<override>():cat = cat{}この場合、AdoptPet() の戻り値の型 cat は、animal に対して共変です。 元々はより汎用的な型を使用していましたが、より具体的な型を使用しています。
これは複合型にも適用できます。 cat の配列では、その配列を使用して animal の配列を初期化できます。 animal はサブクラス cat に変換できないため、逆方向は機能しません。 より汎用的な型として絞り込まれた型を扱うため、cat の配列は、animal の配列に対して共変です。
CatArray:[]cat = array{}
AnimalArray:[]animal = CatArray関数への入力は、共変的に使用することはできません。 次のコードは、AnimalExample() の CatExample() への割り当てが cat 型のため失敗します。これは AnimalExample() の戻り値の型にするには具体的すぎるためです。 この順序を逆にして CatExample() を AnimalExample に割り当てる場合、cat は animal のサブタイプのため、機能します。
CatExample:type{CatFunction(MyCat:cat):void} = …
AnimalExample:type{AnimalFunction(MyAnimal:animal):void} = CatExample次の例では、変数 t が共変的にのみ使用されています。
# The line below will fail because t is used only covariantly.
MyFunction(:logic where t:type):?t = false反変性
反変性とは共変性の反対で、具体的なものを期待しているときにより汎用的なものを使用することを意味します。 これは通常、関数への入力です。 次に示す汎用的なパラメトリック型の例では、payload が反変的に作用しています。
DoSomething(Payload:payload where payload:type):voidたとえば、先ほどの動物保護施設には、新しいネコを取り扱う特定の手順があるとします。 新しいメソッドを pet_sanctuary に追加して「RegisterCat()」という名前を付けます。
pet_sanctuary := class:
AdoptPet():animal = animal{}
RegisterCat(NewAnimal:cat):void = {}cat_sanctuary ではこのメソッドをオーバーライドし、型パラメータとして animal を受け入れます。これは、すべての cat が animal であることが、すでにわかっているためです。
cat_sanctuary := class(pet_sanctuary):
AdoptPet<override>():cat = cat{}
RegisterCat<override>(NewAnimal:animal):void = {}ここで animal は cat に対して反変です。これは、より具体的なものが機能するときに、より汎用的なものを使用するためです。
where 句によって導入された暗黙的な型を使用することにより、共変的にエラーが発生します。 たとえば、この payload は反変的に使用されていますが、引数として定義されていないため、エラーが発生します。
DoSomething(:logic where payload:type) : ?payload = falseこれを修正するため、次のように書き換えて型パラメータを除外できます。
DoSomething(:logic) : ?false = false反変のみの使用ではエラーになりませんが、false の代わりに any を使用して書き換えることができます。 例:
ReturnFirst(First:first_item, :second_item where first_item:type, second_item:type) : first_item = Firstsecond_item は型 type であり、返されなかったため、2 番目の例では any に置き換えて、型のチェックを行わないようにできます。
ReturnFirst(First:first_item, :any where first_item:type) : first_item = First型 first_item を any か false で置き換えると精度が失われます。 たとえば、次のコードではコンパイルが失敗します。
ReturnFirst(First:any, :any) :any = First
Main() : void =
FirstInt:int = ReturnFirst(1, "ignored")既知の制限事項
データ型に関して、明示的な型パラメータは、インターフェースや構造体ではなく、クラスでのみ使用できます。 また、パラメトリック型に関連する継承も許可されていません。 | Verse |
パラメトリック型は再帰が直接的である限り、再帰的に自身を参照できます。 パラメトリック型は他のパラメトリック型を再帰的に参照できません。 | Verse |
現在、クラスは不変のパラメトリック型データのみをサポートします。 たとえば、このコードは | Verse |
明示的な型パラメータは、暗黙的な型パラメータを関数と組み合わせることができるのと同じように、クラスと自由に組み合わせることが可能です。 | Verse |