Dela via


Undertyp och varians

Q# stöder endast ett fåtal konverteringsmekanismer. Implicita konverteringar kan bara ske när du tillämpar binära operatorer, utvärderar villkorsuttryck eller skapar en matrisliteral. I dessa fall bestäms en vanlig supertyp och de nödvändiga konverteringarna utförs automatiskt. Förutom sådana implicita konverteringar är explicita konverteringar via funktionsanrop möjliga och ofta nödvändiga.

För närvarande gäller den enda undertypsrelationen som finns för åtgärder. Intuitivt är det klokt att man ska kunna ersätta en åtgärd som stöder mer än den nödvändiga uppsättningen funktorer. För två betongtyper TIn och TOutär undertyprelationen konkret

    (TIn => TOut) :>
    (TIn => TOut is Adj), (TIn => TOut is Ctl) :>
    (TIn => TOut is Adj + Ctl)

där A :> B anger att B är en undertyp av A. Frasat på ett annat sätt, B är mer restriktivt än A så att ett värde av typen B kan användas där ett värde av typen A krävs. Om en anropningsbar använder ett argument (objekt) av typen Akan ett argument av typen på ett säkert sätt B ersättas eftersom om innehåller alla nödvändiga funktioner.

Den här typen av polymorfism utökas till tupplar eftersom en tupplar av typen B är en undertyp av en tupplartyp A om den innehåller samma antal objekt och typen av varje objekt är en undertyp av motsvarande objekttyp i A. Detta kallas djupundertyp. Det finns för närvarande inget stöd för undertyp för bredd, det vill sägs att det inte finns någon undertypsrelation mellan två användardefinierade typer eller en användardefinierad typ och någon inbyggd typ. Förekomsten av operatorn unwrap , som gör att du kan extrahera en tupplar som innehåller alla namngivna och anonyma objekt, förhindrar detta.

Anteckning

När det gäller anropsbara objekt, om ett anropbart bearbetar ett argument av typen A, kan det också bearbeta ett argument av typen B. Om en anropningsbar skickas som ett argument till en annan anropsbar, måste den kunna bearbeta allt som typsignaturen kan kräva. Det innebär att om den anropbara måste kunna bearbeta ett argument av typen Bkan alla anropbara objekt som kan bearbeta ett mer allmänt argument av typen A skickas på ett säkert sätt. Däremot förväntar vi oss att om vi kräver att den anropsbara koden returnerar ett värde av typen Aräcker det med löftet att returnera ett värde av typen B , eftersom det värdet ger alla nödvändiga funktioner.

Åtgärden eller funktionstypen är kontravariant i argumenttypen och kovariant i sin returtyp. A :> B innebär därför att för alla konkreta typer T1av

    (B → T1) :> (A → T1), and
    (T1 → A) :> (T1 → B) 

här kan betyda antingen en funktion eller åtgärd, och vi utelämnar eventuella anteckningar för egenskaper. Att ersätta A med (T2 → A)(B → T2) respektive och ersätta B med (A → T2) respektive (T2 → B) leder till slutsatsen att för varje konkret typ T2,

    ((A → T2) → T1) :> ((B → T2) → T1), and
    ((T2 → B) → T1) :> ((T2 → A) → T1), and
    (T1 → (B → T2)) :> (T1 → (A → T2)), and
    (T1 → (T2 → A)) :> (T1 → (T2 → B)) 

Genom induktion följer att varje ytterligare indirektion vänder variansen för argumenttypen och lämnar variansen för returtypen oförändrad.

Anteckning

Detta gör det också tydligt vad matrisernas variansbeteende behöver vara. hämtning av objekt via en objektåtkomstoperator motsvarar att anropa en funktion av typen (Int -> TItem), där TItem är typen av element i matrisen. Eftersom den här funktionen implicit skickas när en matris skickas följer det att matriser måste vara kovarianser i sin objekttyp. Samma överväganden gäller även för tupplar, som är oföränderliga och därmed kovarians när det gäller varje objekttyp. Om matriser inte var oföränderliga skulle förekomsten av en konstruktion som gör att du kan ange objekt i en matris och därmed ta ett argument av typen TItem, innebära att matriser också måste vara kontravarianta. Det enda alternativet för datatyper som stöder att hämta och ange objekt är därför ofarlig, vilket innebär att det inte finns någon undertyprelation alls. B[] är inte en undertyp av A[] även om B är en undertyp av A. Trots att matriser i Q# är oföränderliga är de oföränderliga snarare än varianta. Det innebär till exempel att ett värde av typen (Qubit => Unit is Adj)[] inte kan skickas till ett anropsbart värde som kräver ett argument av typen (Qubit => Unit)[]. Att hålla matriser invarianta ger mer flexibilitet i samband med hur matriser hanteras och optimeras i körningen, men det kan vara möjligt att ändra det i framtiden.