型のパラメーター化
Q# は、型パラメーター化された演算と関数をサポートします。
Q# 標準ライブラリでは、型パラメーター化された callable を多用して、関数型言語でおなじみの Mapped
や Fold
などの関数を含む、多数の便利な抽象化を提供します。
型パラメーター化の概念を動機付けるために、関数 Mapped
の例を考えてみます。これは、指定された関数を配列内の各値に適用し、計算値を含む新しい配列を返します。 この機能は、入出力配列の項目の種類を指定せずに完全に記述できます。 厳密な型は関数 Mapped
の実装を変更しないため、任意の項目の型に対してこの実装を定義できるようにすることが理にかなっています。入出力配列内の項目の具象型が指定され、対応する関数の実装が返される "ファクトリ" または "テンプレート" を定義します。 この概念は、型パラメーターの形式で形式化されています。
具象化
演算または関数の宣言では、callable の入力または出力 (またはその両方) の型または型の一部として使用できる 1 つ以上の型パラメーターを指定できます。 例外はエントリ ポイントです。これは具象である必要があり、型パラメーター化することはできません。 型パラメーター名は、ティック (') で始まり、入力と出力の型に複数回出現できます。 callable シグネチャ内の同じ型パラメーターに対応するすべての引数は、同じ型である必要があります。
型パラメーター化された callable は、すべての型パラメーターを具象型に置き換えることができるように、引数として割り当てるか渡す前に具象化する (つまり、必要な型引数を指定する) 必要があります。 型は、組み込み型のいずれか、ユーザー定義型、または現在のスコープ内で具象である場合、具象と見なされます。 次の例では、型が現在のスコープ内で具象であるという意味について示しており、下でより詳細に説明されています。
function Mapped<'T1, 'T2> (
mapper : 'T1 -> 'T2,
array : 'T1[]
) : 'T2[] {
mutable mapped = new 'T2[Length(array)];
for (i in IndexRange(array)) {
set mapped w/= i <- mapper(array[i]);
}
return mapped;
}
function AllCControlled<'T3> (
ops : ('T3 => Unit)[]
) : ((Bool,'T3) => Unit)[] {
return Mapped(CControlled<'T3>, ops);
}
CControlled
関数は Microsoft.Quantum.Canon
名前空間で定義されています。 これは、型 'TIn => Unit
の演算 op
を引数として受け取り、古典ビット (型 Bool
) が true に設定されている場合に元の演算を適用する型 (Bool, 'TIn) => Unit
の新しい演算を返します。これは、op
の古典的に制御されたバージョンと呼ばれることがよくあります。
関数 Mapped
は、任意の項目型 'T1
の配列を引数として受け取り、指定された mapper
関数を各項目に適用し、マップされた項目を格納している型 'T2[]
の新しい配列を返します。 これは Microsoft.Quantum.Array
名前空間で定義されています。 この例では、両方の関数の型パラメーターに同じ名前を指定することで説明が混乱しないように、型パラメーターに番号が付けられています。 これは必要ありません。異なる callable の型パラメーターは同じ名前を持つことができ、選択した名前はその callable の定義内でのみ表示され、関連します。
関数 AllCControlled
は、演算の配列を受け取り、これらの演算の古典的に制御されたバージョンを格納する新しい配列を返します。
Mapped
の呼び出しでは、その型パラメーター 'T1
は 'T3 => Unit
に解決され、型パラメーター 'T2
は (Bool,'T3) => Unit
に解決されます。 解決する型引数は、指定された引数の型に基づいてコンパイラによって推論されます。 これを、呼び出し式の引数によって "暗黙的" に定義されていると言います。 型引数は、同じ行の CControlled
に対して実行されるように明示的に指定することもできます。 型引数を推論できない場合は、明示的な具象化 CControlled<'T3>
が必要です。
型 'T3
は、AllCControlled
の各 "呼び出し" で認識されているため、AllCControlled
のコンテキスト内で具象です。 つまり、型パラメーター化できないプログラムのエントリ ポイントが認識されるとすぐに、AllCControlled
への各呼び出しの具象型 'T3
が認識されるので、特定の型解決に対する適切な実装を生成できます。 プログラムへのエントリ ポイントが認識されると、コンパイル時に型パラメーターのすべての使用を排除できます。 このプロセスを "モノモーフィゼーション" と呼びます。
実行時のみではなくコンパイル時にこれを確実に実行できるようにするには、いくつかの制限が必要です。
制限
次の例を確認してください。
operation Foo<'TArg> (
op : 'TArg => Unit,
arg : 'TArg
) : Unit {
let cbit = RandomInt(2) == 0;
Foo(CControlled(op), (cbit, arg));
}
Foo
の呼び出しによって無限ループが発生することを無視すると、実証できます。
Foo
は、渡された元の演算 op
の古典的に制御されたバージョンと、元の引数に加えてランダムな古典ビットを含むタプルを使用して、自身を呼び出します。
再帰の各反復処理では、次の呼び出しの型パラメーター 'TArg
は (Bool, 'TArg)
に解決されます。ここで、'TArg
は現在の呼び出しの型パラメーターです。 具体的には、Foo
が演算 H
と型 Qubit
の引数 arg
を使用して呼び出されたと します。 次に、Foo
は型引数 (Bool, Qubit)
を使用して自身を呼び出し、その後、それは型引数 (Bool, (Bool, Qubit))
を使用して Foo
を呼び出します。 当然ながら、この場合 Foo
はコンパイル時にモノモーフィゼーション化することはできません。
型パラメーター化された callable のみを含む呼び出しグラフのサイクルには、追加の制限が適用されます。 各 callable は、サイクルを走査した後、同じ型引数のセットを使用して呼び出す必要があります。
Note
制限を低くして、サイクル内の各 callable に一定の数のサイクルを必要にすることができます。その後、次の関数のように、元の型引数のセットを使用して呼び出されます。
function Bar<'T1,'T2,'T3>(a1:'T1, a2:'T2, a3:'T3) : Unit{
Bar<'T2,'T3,'T1>(a2, a3, a1);
}
わかりやすくするために、より制限の厳しい要件が適用されています。 型パラメーターのない少なくとも 1 つの具象 callable が含まれているサイクルでは、このような callable によって、そのサイクル内の型パラメーター化 callable が常に固定された型引数のセットを使用して呼び出されるようになることに注意してください。