Partilhar via


Subtipo e variação

Q# suporta apenas alguns mecanismos de conversão. As conversões implícitas podem acontecer somente ao aplicar operadores binários, avaliar expressões condicionais ou construir um literal de matriz. Nestes casos, um supertipo comum é determinado e as conversões necessárias são realizadas automaticamente. Além dessas conversões implícitas, conversões explícitas por meio de chamadas de função são possíveis e muitas vezes necessárias.

Atualmente, a única relação de subtipagem que existe aplica-se às operações. Intuitivamente, faz sentido que se possa substituir uma operação que suporte mais do que o conjunto necessário de functores. Concretamente, para quaisquer dois tipos concretos TIn e TOut, a relação de subtipagem é

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

onde A :> B indica que B é um subtipo de A. Formulado de forma diferente, B é mais restritivo do que A tal que um valor de tipo B pode ser usado sempre que um valor de tipo A é necessário. Se um chamável depende de um argumento (item) de ser do tipo A, então um argumento do tipo B pode ser substituído com segurança, uma vez que se fornece todos os recursos necessários.

Este tipo de polimorfismo estende-se às tuplas na medida em que uma tupla do tipo B é um subtipo de um tipo de tupla A se contém o mesmo número de itens e o tipo de cada item é um subtipo do tipo de item correspondente em A. Isso é conhecido como subtipagem de profundidade. Atualmente, não há suporte para de subtipagem de largura, ou seja, não há nenhuma relação de subtipo entre dois tipos struct ou um tipo struct e qualquer tipo interno. A existência do operador unwrap, que permite extrair uma tupla contendo todos os itens nomeados, impede isso.

Observação

Em relação aos chamáveis, se um chamável processa um argumento do tipo A, então ele também é capaz de processar um argumento do tipo B. Se um exigível é passado como um argumento para outro chamável, então ele tem que ser capaz de processar qualquer coisa que a assinatura de tipo possa exigir. Isso significa que, se o chamável precisa ser capaz de processar um argumento do tipo B, qualquer chamável que seja capaz de processar um argumento mais geral do tipo A pode ser passado com segurança. Por outro lado, esperamos que, se exigirmos que o chamável passado retorne um valor do tipo A, então a promessa de retornar um valor do tipo B é suficiente, uma vez que esse valor fornecerá todos os recursos necessários.

O tipo de operação ou função é contravariante em seu tipo de argumento e covariante em seu tipo de retorno. A :> B implica, portanto, que, para qualquer tipo concreto T1,

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

onde aqui pode significar uma função ou operação, e omitimos quaisquer anotações para características. A substituição de A por (B → T2) e (T2 → A), respectivamente, e a substituição de B por (A → T2) e (T2 → B), respectivamente, leva à conclusão de que, para qualquer tipo concreto 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)) 

Por indução, segue-se que cada indireção adicional reverte a variância do tipo de argumento e deixa a variância do tipo de retorno inalterada.

Observação

Isso também deixa claro qual deve ser o comportamento de variância de matrizes; Recuperar itens através de um operador de acesso a itens corresponde a invocar uma função do tipo (Int -> TItem), onde TItem é o tipo dos elementos na matriz. Como essa função é implicitamente passada ao passar uma matriz, segue-se que as matrizes precisam ser covariantes em seu tipo de item. As mesmas considerações também valem para as tuplas, que são imutáveis e, portanto, covariáveis em relação a cada tipo de item. Se matrizes não fossem imutáveis, a existência de uma construção que permitiria definir itens em uma matriz e, assim, tomar um argumento do tipo TItem, implicaria que as matrizes também precisam ser contravariantes. A única opção para tipos de dados que suportam a obtenção e configuração de itens é, portanto, ser invariante, o que significa que não há nenhuma relação de subtipagem; B[]não é um subtipo de A[] mesmo que B seja um subtipo de A. Apesar do fato de que as matrizes em Q# são imutáveis, elas são invariantes em vez de covariantes. Isso significa, por exemplo, que um valor do tipo (Qubit => Unit is Adj)[] não pode ser passado para um chamável que requer um argumento do tipo (Qubit => Unit)[]. Manter os arrays invariantes permite mais flexibilidade relacionada à forma como os arrays são manipulados e otimizados no tempo de execução, mas pode ser possível revisar isso no futuro.