Operações e funções

Conforme elaborado mais detalhadamente na descrição do tipo de dados qubit, os cálculos quânticos são executados na forma de efeitos colaterais das operações suportadas nativamente no processador quântico de destino. Estes são, na verdade, os únicos efeitos colaterais em Q#. Uma vez que todos os tipos são imutáveis, não existem efeitos colaterais que afetem um valor explicitamente representado no Q#. Assim, desde que uma implementação de um determinado callable não chame direta ou indiretamente qualquer uma destas operações implementadas nativamente, a sua execução produz sempre o mesmo resultado, dada a mesma entrada.

Q# permite-lhe dividir explicitamente tais cálculos puramente deterministas em funções. Uma vez que o conjunto de instruções suportadas nativamente não é fixo e incorporado no próprio idioma, mas sim totalmente configurável e expresso como uma Q# biblioteca, o determinismo é garantido ao exigir que as funções só possam chamar outras funções e não podem chamar operações. Além disso, as instruções nativas que não são deterministas, ou seja, porque afetam o estado quântico, são representadas como operações. Com estas duas restrições, as funções podem ser avaliadas assim que o valor de entrada for conhecido e, em princípio, nunca precisarem de ser avaliadas mais do que uma vez para a mesma entrada.

Q# por conseguinte, distingue entre dois tipos de callables: operações e funções. Todos os callables assumem um único argumento (potencialmente com valor de cadeia de identificação) como entrada e produzem um único valor (cadeia de identificação) como saída. Sintaticamente, o tipo de operação é expresso como <TIn> => <TOut> is <Char>, onde <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 não precisar de especificar características, a sintaxe simplifica para <TIn> => <TOut>. Da mesma forma, os tipos de funções são expressos como <TIn> -> <TOut>.

Além desta garantia de determinismo, há pouca diferença entre operações e funções. Ambos são valores de primeira classe que podem ser transmitidos livremente; podem ser utilizados como valores ou argumentos devolvidos a outros callables, conforme mostrado no exemplo seguinte:

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

Ambos podem ser instanciados com base numa definição parametrizada por tipo, por exemplo, o tipo de função Powparametrizada acima e podem ser parcialmente aplicados conforme feito na return instrução no exemplo.

Características da operação

Além das informações sobre o tipo de entrada e saída, o tipo de operação contém informações sobre as características de uma operação. Estas informações, por exemplo, descrevem que functores são suportados pela 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 etiquetas predefinidas e incorporadas. São expressas na forma de uma expressão especial que faz parte do tipo assinatura. A expressão consiste num dos conjuntos predefinidos de etiquetas ou numa combinação de expressões de características através de um operador binário suportado.

Existem dois conjuntos predefinidos Adj e Ctl.

  • Adj é o conjunto que contém uma única etiqueta que indica que uma operação é adjacente, o que significa que suporta o Adjoint functor e a transformação quântica aplicada pode ser "anulada", ou seja, pode ser invertida.
  • Ctl é o conjunto que contém uma única etiqueta que indica que uma operação é controlável, o que significa que suporta o Controlled functor e a respetiva execução pode ser condicionada no estado de outros qubits.

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

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

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

Nota

Indicar as características de uma operação neste formulário tem duas vantagens importantes; para uma, podem ser introduzidas novas etiquetas sem ter exponencialmente muitas palavras-chave de idioma para todas as combinações de etiquetas. Talvez o mais importante, utilizar expressões para indicar as características de uma operação também suporta parâmetros sobre características de operação no futuro.