Declarações que podem ser chamadas

Declarações callable ou callables declaradas em um escopo global são publicamente visíveis por padrão; ou seja, eles podem ser usados em qualquer lugar no mesmo projeto e em um projeto que faz referência ao assembly no qual são declarados. Os modificadores de acesso permitem restringir sua visibilidade somente para o assembly atual, de modo que os detalhes da implementação possam ser alterados posteriormente, sem quebrar o código que se baseia em uma biblioteca específica.

Q# dá suporte a dois tipos de callables: operações e funções. O tópico Operações e Funções fornece a diferença entre os dois. Q# também dá suporte à definição de modelos; por exemplo, implementações com parâmetros de tipo para um determinado callable. Para saber mais, confira Parametrizações de tipo.

Observação

Essas implementações de tipo parametrizadas não podem usar nenhuma construção de linguagem que dependa de propriedades específicas dos argumentos de tipo; atualmente, não há como expressar restrições de tipo no Q# ou definir implementações especializadas para argumentos de tipo específicos.

Callables e functors

Q# permite implementações especializadas para fins específicos; por exemplo, as operações no Q# podem definir implicitamente ou explicitamente o suporte para determinados functors e, juntamente com eles, as implementações especializadas para invocar quando um functor específico é aplicado a esse callable.

Uma functor, de certa forma, é um alocador que define uma nova implementação callable que tem uma relação específica com a callable aplicada. Functors são mais do que as funções tradicionais de nível superior, pois exigem acesso aos detalhes da callable na qual eles foram aplicados. Nesse sentido, eles são semelhantes a outros alocadores, como modelos. Eles também podem ser aplicados a callables com parâmetros de tipo.

Considere a seguinte operação, ApplyQFT:

    operation ApplyQFT(qs : Qubit[]) : Unit is Adj + Ctl {
        let length = Length(qs);
        Fact(length >= 1, "ApplyQFT: Length(qs) must be at least 1.");
        for i in length - 1..-1..0 {
            H(qs[i]);
            for j in 0..i - 1 {
                Controlled R1Frac([qs[i]], (1, j + 1, qs[i - j - 1]));
            }
        }
    }

Essa operação usa um argumento do tipo Qubit[] e retorna um valor do tipo Unit. A anotação is Adj + Ctl na declaração de ApplyQFT indica que a operação dá suporte ao functor Adjoint e Controlled. (Para obter mais informações, consulte Características da operação). A expressão Adjoint ApplyQFT acessa a especialização que implementa o adjacente de ApplyQFTe Controlled ApplyQFT acessa a especialização que implementa a versão controlada do ApplyQFT. Além do argumento da operação original, a versão controlada de uma operação usa uma matriz de qubits de controle e aplica a operação original na condição de que todos esses qubits de controle estejam em um estado |1⟩.

Teoricamente, uma operação para a qual uma versão adjacente pode ser definida, também deve ter uma versão controlada e vice-versa. No entanto, na prática, pode ser difícil desenvolver uma implementação para uma ou outra, especialmente para implementações probabilísticas após um padrão de repeat-until-success. Por esse motivo, o Q# permite que você declare o suporte para cada functor individualmente. No entanto, como os dois functors se deslocam, uma operação que define o suporte para ambos também precisa ter uma implementação (normalmente definida implicitamente, o que significa que é gerada por compilador) para quando ambos functors forem aplicados à operação.

Não há funtores que possam ser aplicados a funções. Atualmente, as funções têm exatamente uma implementação de corpo e nenhuma especialização adicional. Por exemplo, a declaração

    function Hello (name : String) : String {
        $"Hello, {name}!"
    }

é equivalente a

    function Hello (name : String) : String {
        body ... {
            $"Hello, {name}!"
        }
    }

Aqui, body especifica que a implementação fornecida se aplica ao corpo padrão da função Hello, o que significa que a implementação é invocada quando não houver functors ou outros mecanismos de fábrica aplicados antes da invocação. Os três pontos em body ... correspondem a uma diretiva do compilador indicando que os itens de argumento na declaração de função devem ser copiados e colados nesse spot.

Os motivos por trás de indicar explicitamente onde os argumentos da declaração callable pai devem ser copiados e colados são duplos: um, é desnecessário repetir a declaração de argumento e dois, garante que funtores que exigem argumentos adicionais, como o Controlled functor, possam ser introduzidos de maneira consistente.

Quando há exatamente uma especialização definindo a implementação do corpo padrão, o encapsulamento adicional do formulário body ... { <implementation> } pode ser omitido.

Recursão

Os callables Q# podem ser recursivos direta ou indiretamente e podem ser declarados em qualquer ordem; uma operação ou função pode chamar a si mesma ou outro callable que chame direta ou indiretamente o chamador.

Durante a execução no hardware Quantum, o espaço de pilha pode ser limitado e as recorrências que excederem esse limite do espaço de pilha resultam em um erro de tempo de execução.