Compartilhar via


Subdigitação e variância

O Q# dá suporte apenas a alguns mecanismos de conversão. As conversões implícitas podem ocorrer somente ao aplicar operadores binárias, ao avaliar expressões condicionais e ao construir um literal de matriz. Nesses casos, um supertipo comum é determinado e as conversões necessárias são executadas automaticamente. Além dessas conversões implícitas, as conversões explícitas por chamadas de função são possíveis e, muitas vezes, necessárias.

No momento, a única relação de subtipo existente se aplica a operações. Intuitivamente, faz sentido poder substituir uma operação que dê suporte a um conjunto de functores maior do que o necessário. Concretamente, para os tipos concretos TIn e TOut, a relação de subdigitação é

    (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. Ou seja, B é mais restritivo do que A, de forma que um valor do tipo B possa ser usado sempre que um valor do tipo A for necessário. Se um callable se basear no argumento (item) de ser do tipo A, um argumento do tipo B poderá ser substituído com segurança, pois fornece todos os recursos necessários.

Esse tipo de polimorfismo se aplica às tuplas, pois um tipo de tupla B é um subtipo do tipo de tupla A quando 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 subdigitação de profundidade. No momento, não há suporte para o subtipo de largura. Ou seja, não há nenhuma relação de subtipo entre dois tipos definidos pelo usuário ou um tipo definido pelo usuário e qualquer tipo interno. Isso é impedido pela existência do operador unwrap, que permite extrair uma tupla contendo todos os itens nomeados e anônimos.

Observação

Em relação aos chamadores, se um chamável processar um argumento do tipo A, ele também será capaz de processar um argumento do tipo B. Se um chamável for passado como um argumento para outro chamável, ele precisará ter a capacidade de processar qualquer coisa que a assinatura do tipo exigir. Isso significa que, se o chamável precisar da capacidade de processar um argumento do tipo B, qualquer chamável com a capacidade de processar um argumento mais geral do tipo A poderá ser passado com segurança. Por outro lado, esperamos que, se exigirmos que o chamável passado retorne um valor do tipo A, a promessa de retornar um valor do tipo B seja suficiente, pois esse valor fornecerá todas as funcionalidades necessárias.

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

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

onde aqui pode significar uma função ou operação e omitimos quaisquer anotações das 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 T2 concreto,

    ((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, como consequência, cada indireção adicional reverte a variância do tipo de argumento e deixa a variação do tipo de retorno inalterada.

Observação

Isso também esclarece qual deve ser o comportamento da variação das matrizes; a recuperação de itens por um operador de acesso a itens corresponde à invocação de uma função do tipo (Int -> TItem), em que TItem é o tipo dos elementos na matriz. Como essa função é transmitida implicitamente ao passar uma matriz, como consequência, as matrizes precisam ser covariantes em seu tipo de item. As mesmas considerações também contêm tuplas, que são imutáveis e, portanto, a covariante referente a cada tipo de item. Se as matrizes não fossem imutáveis, a existência de um constructo que permitisse definir itens em uma matriz e, portanto, usar um argumento do tipo TItem, significaria que as matrizes também precisariam ser contravariantes. A única opção para tipos de dados que dão suporte à obtenção e configuração de itens é, portanto, invariável, o que significa que não há nenhuma relação de subdigitação; B[]não é um subtipo de A[], mesmo que B seja um subtipo deA. Apesar das matrizes em Q# serem imutáveis, elas são invariantes em vez de covariantes. Isso, por exemplo, significa que um valor do tipo (Qubit => Unit is Adj)[] não pode ser passado a um chamável que exija um argumento do tipo (Qubit => Unit)[]. Manter as matrizes invariáveis permite mais flexibilidade relacionada ao modo como as matrizes são tratadas e otimizadas no tempo de execução, mas pode ser possível revisá-las no futuro.