Параметризации типов

Q# поддерживает типо-параметризованные операции и функции. Стандартные библиотеки Q# используют многое использование вызовов с параметризованными типами для предоставления большого количества полезных абстракций, включая такие функции, как Mapped и Fold, знакомые с функциональными языками.

Чтобы мотивировать концепцию параметризации типов, рассмотрим пример функции Mapped, которая применяет данную функцию к каждому значению в массиве и возвращает новый массив с вычисляемых значений. Эта функция может быть идеально описана без указания типов элементов входных и выходных массивов. Так как точные типы не изменяют реализацию функции Mapped, имеет смысл определить эту реализацию для произвольных типов элементов; Мы хотим определить фабрику или шаблон , который, учитывая конкретные типы элементов в входном и выходном массиве, возвращает соответствующую реализацию функции. Это понятие формализовано в виде параметров типа.

Сцепить

Любое объявление операции или функции может указывать один или несколько параметров типа, которые можно использовать в качестве типов или части типов, входных или выходных данных вызываемого объекта или оба. Исключения — это точки входа, которые должны быть конкретными и не могут быть параметризованы в типе. Имена параметров типа начинаются с галочки (') и могут отображаться несколько раз в типах входных и выходных данных. Все аргументы, соответствующие одному параметру типа в вызываемой сигнатуре, должны иметь одинаковый тип.

Вызывающий объект с типом должен быть конкретизирован, то есть он должен быть предоставлен с необходимыми аргументами типа, прежде чем его можно назначить или передать в качестве аргумента, таким образом, чтобы все параметры типа можно было заменить конкретными типами. Тип считается конкретным, если он является одним из встроенных типов, типа struct или если он является конкретным в текущей области. В следующем примере показано, что это означает для конкретного типа в текущей области и подробно описано ниже.

    function Mapped<'T1, 'T2> (
        mapper : 'T1 -> 'T2,
        array : 'T1[]
    ) : 'T2[] {

        mutable mapped = new 'T2[Length(array)];
        for (i in IndexRange(array)) {
            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. Он принимает операцию op типа 'TIn => Unit в качестве аргумента и возвращает новую операцию типа (Bool, 'TIn) => Unit, которая применяет исходную операцию, если классический бит (типа Bool) имеет значение true; это часто называется классическо управляемой версией op.

Функция Mapped принимает массив произвольного типа элемента 'T1 в качестве аргумента, применяет данную функцию mapper к каждому элементу и возвращает новый массив типов 'T2[], содержащий сопоставленные элементы. Он определен в пространстве имен Microsoft.Quantum.Array. В этом примере параметры типа нумеруются, чтобы избежать путаницы обсуждения, предоставляя параметры типа в обоих функциях одинаковое имя. Это не обязательно; Параметры типа для разных вызываемых объектов могут иметь одно и то же имя, и выбранное имя отображается только в определении вызываемого объекта.

Функция AllCControlled принимает массив операций и возвращает новый массив, содержащий классически управляемые версии этих операций. Вызов Mapped разрешает 'T1 параметра типа 'T3 => Unit, а параметр типа 'T2(Bool,'T3) => Unit. Аргументы разрешающего типа выводятся компилятором на основе типа заданного аргумента. Мы говорим, что они неявно, определенные аргументом выражения вызова. Аргументы типа также можно указать явным образом, как и для CControlled в той же строке. Явный CControlled<'T3> сцепления необходим, если аргументы типа не могут быть выведены.

Тип 'T3 является конкретным в контексте AllCControlled, так как он известен для каждого вызоваAllCControlled. Это означает, что как только точка входа программы , которая не может быть типом параметризована, известна, поэтому конкретный тип 'T3 для каждого вызова AllCControlled, таким образом, подходящую реализацию для этого конкретного разрешения типов можно создать. После того как точка входа в программу известна, все использование параметров типа можно устранить во время компиляции. Мы называем этот процесс мономорфизации.

Некоторые ограничения необходимы, чтобы убедиться, что это действительно можно сделать во время компиляции, а не только во время выполнения.

Ограничения

Рассмотрим следующий пример:

    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 операции и аргументом arg типа Qubit. Foo затем вызывается с аргументом типа (Bool, Qubit), который затем вызывает Foo с аргументом типа (Bool, (Bool, Qubit))и т. д. Очевидно, что в этом случае Foo нельзя мономорфизировать во время компиляции.

Дополнительные ограничения применяются к циклам в графе вызовов, включающих только вызываемые вызовы с параметризованными типами. Каждый вызываемый объект должен вызываться с одинаковым набором аргументов типа после обхода цикла.

Примечание.

Можно было бы быть менее строгим и требовать, чтобы для каждого вызываемого цикла было бы ограниченное число циклов, после которого он вызывается с исходным набором аргументов типа, например для следующей функции:

   function Bar<'T1,'T2,'T3>(a1:'T1, a2:'T2, a3:'T3) : Unit{
       Bar<'T2,'T3,'T1>(a2, a3, a1);
   }

Для простоты применяется более строгое требование. Обратите внимание, что для циклов, включающих по крайней мере один конкретный вызываемый параметр без какого-либо параметра типа, такой вызывающий элемент гарантирует, что вызываемые типом параметризованные вызовы в этом цикле всегда вызываются с фиксированным набором аргументов типа.