Operações e funções

Como elaborado em mais detalhes na descrição do tipo de dados qubit, as computações quânticas são executadas na forma de efeitos colaterais de operações que têm suporte nativo no processador quântico de destino. Esses são os únicos efeitos colaterais em Q#. Como todos os tipos são imutáveis, não há nenhum efeito colateral que afete um valor representado explicitamente em Q#. Portanto, desde que a implementação de um determinado chamável não chame direta ou indiretamente nenhuma dessas operações implementadas de modo nativo, sua execução sempre produzirá a mesma saída se tiver a mesma entrada.

O Q# permite dividir explicitamente essas computações puramente determinísticas em funções. Como o conjunto de instruções com suporte nativo não é fixo e integrado à própria linguagem, mas é totalmente configurável e expresso como uma biblioteca Q#, o determinismo é garantido por meio da exigência de que as funções só possam chamar outras funções, mas não possam chamar nenhuma operação. Além disso, as instruções nativas que não são determinísticas, por exemplo, porque afetam o estado quântico, são representadas como operações. Com essas duas restrições, as funções podem ser avaliadas assim que o valor de entrada é conhecido e, em princípio, nunca precisam ser avaliadas mais de uma vez para a mesma entrada.

Portanto, o Q# faz distinção entre dois tipos de chamáveis: operações e funções. Todos os chamáveis usam um só argumento (possivelmente com valor de tupla) como entrada e produzem um só valor (tupla) como saída. Sintaticamente, o tipo de operação é expresso como <TIn> => <TOut> is <Char>, em que <TIn> deve ser substituído pelo tipo de argumento, <TOut> deve ser substituído pelo tipo de retorno e <Char> deve ser substituído pelas características da operação. Se nenhuma característica precisar ser especificada, a sintaxe será simplificada para <TIn> => <TOut>. Da mesma forma, os tipos de função são expressos como <TIn> -> <TOut>.

Além dessa garantia de determinismo, há poucas diferenças entre operações e funções. Ambas são valores de primeira classe que podem ser passados livremente. Elas podem ser usadas como valores retornados ou argumentos para outros chamáveis, como ilustra o seguinte exemplo:

function Pow<'T>(op : 'T => Unit, pow : Int) : 'T => Unit {
    return PowImpl(op, pow, _);
}

É possível criar instâncias de ambas com base em uma definição parametrizada por tipo, como a função parametrizada por tipoPow acima, e elas podem ser aplicadas parcialmente, como na instrução return do exemplo.

Características da operação

Além das informações sobre o tipo de entrada e de saída, o tipo de operação contém informações sobre as características de uma operação. Essas informações descrevem, por exemplo, quais functores são compatíveis com a operação. Além disso, a representação interna também contém informações relevantes para otimização que são inferidas pelo compilador.

As características de uma operação são um conjunto de rótulos predefinidos e integrados. Eles são expressos na forma de uma expressão especial que faz parte da assinatura de tipo. A expressão consiste em um dos conjuntos predefinidos de rótulos ou de uma combinação de expressões de características por meio de um operador binário com suporte.

Há dois conjuntos predefinidos, Adj e Ctl.

  • Adj é o conjunto que contém um só rótulo que indica se uma operação pode ser adjunta, ou seja, que ela é compatível com o functor Adjoint e que a transformação quântica aplicada pode ser "desfeita", ou seja, invertida.
  • Ctl é o conjunto que contém um só rótulo que indica se uma operação é controlável, ou seja, que ela é compatível com o functor Controlled e que sua execução pode ser condicionada no estado de outros qubits.

Os dois operadores com suporte como parte das expressões de características são a união de conjunto + e a interseção de conjunto *. No EBNF,

    predefined = "Adj" | "Ctl";
    characteristics = predefined 
        | "(", characteristics, ")" 
        | characteristics ("+"|"*") characteristics;

Como esperado, * tem precedência maior do que + e ambos são associativos à esquerda. O tipo de uma operação unitária, por exemplo, é expresso como <TIn> => <TOut> is Adj + Ctl em que <TIn> deve ser substituído pelo tipo do argumento de operação e <TOut> pelo tipo do valor retornado.

Observação

Indicar que as características de uma operação nesse formato têm duas vantagens principais. Novos rótulos podem ser introduzidos sem ter, exponencialmente, muitas palavras-chave de linguagem para todas as combinações de rótulos, por exemplo. Ainda mais importante é que o uso de expressões para indicar as características de uma operação também dá suporte a parametrizações de características da operação no futuro.