Partilhar via


Operações e funções

Conforme elaborado em mais detalhes na descrição do tipo de dados qubit, os cálculos quânticos são executados na forma de efeitos colaterais de operações que são suportadas nativamente no processador quântico alvo. Estes são, de facto, os únicos efeitos secundários em Q#. Como todos os tipos são imutáveis, não há efeitos colaterais que afetem um valor que é explicitamente representado em Q#. Assim, desde que uma implementação de um determinado chamável não chame direta ou indiretamente nenhuma dessas operações nativamente implementadas, sua execução sempre produz a mesma saída, dada a mesma entrada.

Q# permite que você divida explicitamente esses cálculos puramente determinísticos em funções . Uma vez que o conjunto de instruções suportadas nativamente não é fixo e incorporado na linguagem em si, mas sim totalmente configurável e expresso como uma biblioteca Q#, o determinismo é garantido exigindo que as funções só possam chamar outras funções e não possam chamar nenhuma operação. Além disso, instruções nativas que não são determinísticas, ou seja, porque impactam o estado quântico, são representadas como operações. Com essas duas restrições, as funções podem ser avaliadas assim que seu valor de entrada é conhecido e, em princípio, nunca precisam ser avaliadas mais de uma vez para a mesma entrada.

Q# distingue, portanto, dois tipos de exigíveis: operações e funções. Todos os chamáveis tomam um único argumento (potencialmente com valor de tupla) como entrada e produzem um único valor (tupla) 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 nenhuma característica precisar ser especificada, a sintaxe simplifica para <TIn> => <TOut>. Da mesma forma, os tipos de função são expressos como <TIn> -> <TOut>.

Além dessa garantia de determinismo, há pouca diferença entre operações e funções. Ambos são valores de primeira classe que podem ser passados livremente; Eles podem ser usados como valores de retorno ou argumentos para outros chamáveis, conforme mostrado no exemplo a seguir:

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

Ambos podem ser instanciados com base em uma definição parametrizada de tipo, por exemplo, a função tipo parametrizado Pow anteriormente, e eles podem ser parcialmente aplicados como feito na instrução return no exemplo.

Características de funcionamento

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. Essas informações, por exemplo, descrevem quais 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 rótulos predefinidos e integrados. Eles são expressos na forma de uma expressão especial que faz parte da assinatura do tipo. A expressão consiste em um dos conjuntos predefinidos de rótulos ou em uma 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 um único rótulo indicando que uma operação é adjuntável, o que significa que suporta o functor Adjoint e a transformação quântica aplicada pode ser "desfeita", ou seja, pode ser invertida.
  • Ctl é o conjunto que contém um único rótulo indicando que uma operação é controlável, o que significa que suporta o functor Controlled e sua execução pode ser condicionada ao estado de outros qubits.

Os dois operadores que são suportados como parte de expressões de características são o conjunto união + e o conjunto interseção *. Na EBNF (forma Extended Backus-Naur),

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

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

Observação

Indicar as características de uma operação desta forma tem duas grandes vantagens; Por um lado, novos rótulos podem ser introduzidos sem ter exponencialmente muitas palavras-chave linguísticas para todas as combinações de rótulos. Mais importante ainda, o uso de expressões para indicar as características de uma operação também suporta parametrizações sobre as características da operação no futuro.