Parameterisasi jenis

Q# mendukung operasi dan fungsi berparameter jenis. Pustaka standar Q# memanfaatkan panggilan berparametri jenis yang berat untuk menyediakan sejumlah abstraksi yang berguna, termasuk fungsi seperti Mapped dan Fold yang akrab dari bahasa fungsional.

Untuk memotivasi konsep parameterisasi jenis, pertimbangkan contoh fungsi Mapped, yang menerapkan fungsi tertentu ke setiap nilai dalam array dan mengembalikan array baru dengan nilai komputasi. Fungsionalitas ini dapat dijelaskan dengan sempurna tanpa menentukan jenis item array input dan output. Karena jenis yang tepat tidak mengubah implementasi fungsi Mapped, masuk akal bahwa seharusnya dimungkinkan untuk menentukan implementasi ini untuk jenis item arbitrer; kami ingin menentukan pabrik atau templat bahwa, mengingat jenis konkret untuk item dalam array input dan output, mengembalikan implementasi fungsi yang sesuai. Gagasan ini diformalkan dalam bentuk parameter jenis.

Konkretisasi

Setiap operasi atau deklarasi fungsi dapat menentukan satu atau beberapa parameter jenis yang dapat digunakan sebagai jenis, atau bagian dari jenis, dari input atau output yang dapat dipanggil, atau keduanya. Pengecualian adalah titik masuk, yang harus konkret dan tidak dapat diparmetriskan jenis. Ketik nama parameter dimulai dengan tanda centang (') dan mungkin muncul beberapa kali dalam jenis input dan output. Semua argumen yang sesuai dengan parameter jenis yang sama dalam tanda tangan yang dapat dipanggil harus berjenis yang sama.

Panggilan berparameter jenis perlu disadari, yaitu, harus disediakan dengan argumen jenis yang diperlukan sebelum dapat ditetapkan atau diteruskan sebagai argumen, sehingga semua parameter jenis dapat diganti dengan jenis beton. Jenis dianggap konkret jika merupakan salah satu jenis bawaan, jenis struct, atau jika konkret dalam cakupan saat ini. Contoh berikut menggambarkan apa artinya jenis menjadi konkret dalam cakupan saat ini, dan dijelaskan secara lebih rinci di bawah ini:

    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); 
    }

Fungsi CControlled didefinisikan dalam namespace Microsoft.Quantum.Canon. Dibutuhkan operasi op jenis 'TIn => Unit sebagai argumen dan mengembalikan operasi baru jenis (Bool, 'TIn) => Unit yang menerapkan operasi asli, menyediakan bit klasik (jenis Bool) diatur ke benar; ini sering disebut sebagai versi opyang dikontrol secara klasifikasi.

Fungsi Mapped mengambil array jenis item arbitrer 'T1 sebagai argumen, menerapkan fungsi mapper yang diberikan ke setiap item, dan mengembalikan array jenis baru 'T2[] yang berisi item yang dipetakan. Ini didefinisikan dalam namespace Microsoft.Quantum.Array. Untuk tujuan contoh, parameter jenis diberi nomor untuk menghindari membuat diskusi lebih membingungkan dengan memberikan parameter jenis di kedua fungsi dengan nama yang sama. Ini tidak diperlukan; parameter jenis untuk panggilan yang berbeda mungkin memiliki nama yang sama, dan nama yang dipilih hanya terlihat dan relevan dalam definisi yang dapat dipanggil.

Fungsi AllCControlled mengambil array operasi dan mengembalikan array baru yang berisi versi operasi ini yang dikontrol secara klasifikasi. Panggilan Mapped menyelesaikan parameter jenisnya 'T1 ke 'T3 => Unit, dan parameter jenisnya 'T2 ke (Bool,'T3) => Unit. Argumen jenis penyelesaian disimpulkan oleh pengkompilasi berdasarkan jenis argumen yang diberikan. Kami mengatakan bahwa mereka secara implisit didefinisikan oleh argumen ekspresi panggilan. Argumen jenis juga dapat ditentukan secara eksplisit, seperti yang dilakukan untuk CControlled dalam baris yang sama. CControlled<'T3> konkretisasi eksplisit diperlukan ketika argumen jenis tidak dapat disimpulkan.

Jenis 'T3 konkret dalam konteks AllCControlled, karena dikenal untuk setiap pemanggilanAllCControlled. Itu berarti bahwa segera setelah titik masuk program - yang tidak dapat diparmetriz jenis - diketahui, begitu juga jenis konkret 'T3 untuk setiap panggilan ke AllCControlled, sehingga implementasi yang sesuai untuk resolusi jenis tertentu dapat dihasilkan. Setelah titik masuk ke program diketahui, semua penggunaan parameter jenis dapat dihilangkan pada waktu kompilasi. Kami menyebut proses ini sebagai monomorfisasi .

Beberapa pembatasan diperlukan untuk memastikan bahwa ini memang dapat dilakukan pada waktu kompilasi dibandingkan hanya pada waktu proses.

Batasan

Pertimbangkan contoh berikut:

    operation Foo<'TArg> (
        op : 'TArg => Unit,
        arg : 'TArg
    ) : Unit {

        let cbit = RandomInt(2) == 0;
        Foo(CControlled(op), (cbit, arg));        
    } 

Mengabaikan bahwa pemanggilan Foo menghasilkan perulangan tak terbatas, itu berfungsi untuk tujuan ilustrasi. Foo memanggil dirinya sendiri dengan versi yang dikontrol secara klasik dari op operasi asli yang telah diteruskan, serta tuple yang berisi bit klasik acak selain argumen asli.

Untuk setiap perulangan dalam rekursi, parameter jenis 'TArg panggilan berikutnya diselesaikan ke (Bool, 'TArg), di mana 'TArg adalah parameter jenis panggilan saat ini. Secara konkret, misalkan Foo dipanggil dengan H operasi dan argumen arg jenis Qubit. Foo kemudian memanggil dirinya sendiri dengan argumen jenis (Bool, Qubit), yang kemudian memanggil Foo dengan argumen jenis (Bool, (Bool, Qubit)), dan sebagainya. Jelas, dalam hal ini Foo tidak dapat dimonopoli pada waktu kompilasi.

Pembatasan tambahan berlaku untuk siklus dalam grafik panggilan yang hanya melibatkan panggilan berparmetri jenis. Setiap yang dapat dipanggil perlu dipanggil dengan serangkaian argumen jenis yang sama setelah melintasi siklus.

Nota

Dimungkinkan untuk menjadi kurang ketat dan mengharuskan setiap yang dapat dipanggil dalam siklus, ada jumlah siklus terbatas setelah itu dipanggil dengan sekumpulan argumen jenis asli, seperti kasus untuk fungsi berikut:

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

Untuk kesederhanaan, persyaratan yang lebih ketat diberlakukan. Perhatikan bahwa untuk siklus yang melibatkan setidaknya satu panggilan konkret tanpa parameter jenis apa pun, seperti itu dapat dipanggil memastikan bahwa callable berparama jenis dalam siklus tersebut selalu dipanggil dengan sekumpulan argumen jenis tetap.