Compartir a través de


Operaciones y funciones

Como se detalla en la descripción del tipo de datos cúbit, los cálculos cuánticos se ejecutan en forma de efectos secundarios de las operaciones que se admiten de forma nativa en el procesador cuántico de destino. Estos son, de hecho, los únicos efectos secundarios en Q#. Puesto que todos los tipos son inmutables, no hay ningún efecto secundario que afecte a un valor que se represente explícitamente en Q#. Por lo tanto, siempre que una implementación de un invocable determinado no llame directa o indirectamente a ninguna de estas operaciones implementadas de forma nativa, su ejecución siempre produce la misma salida dada la misma entrada.

Q# le permite dividir explícitamente estos cálculos puramente deterministas en funciones. Dado que el conjunto de instrucciones admitidas de forma nativa no es fijo ni está integrado en el propio lenguaje, sino que es totalmente configurable y se expresa como una biblioteca de Q#, el determinismo se garantiza con el requisito de que las funciones solo puedan llamar a otras funciones y no puedan llamar a ninguna operación. Además, las instrucciones nativas que no son deterministas, por ejemplo, porque afectan al estado cuántico, se representan como operaciones. Con estas dos restricciones, las funciones se pueden evaluar en cuanto se conoce su valor de entrada y, en principio, no es necesario evaluarla más de una vez para la misma entrada.

Por lo tanto, Q# distingue entre dos tipos de invocables: operaciones y funciones. Todos los invocables toman un único argumento (potencialmente con valores de tupla) como entrada y generan un valor único (tupla) como salida. Sintácticamente, el tipo de operación se expresa como <TIn> => <TOut> is <Char>, donde <TIn> se reemplazará por el tipo de argumento, <TOut> se reemplazará por el tipo de valor devuelto, y <Char> se reemplazará por las características de la operación. Si no es necesario especificar ninguna característica, la sintaxis se simplifica a <TIn> => <TOut>. De forma similar, los tipos de función se expresan como <TIn> -> <TOut>.

Aparte de esta garantía de determinismo, hay poca diferencia entre las operaciones y las funciones. Ambos son valores de primera clase que se pueden pasar libremente; se pueden usar como valores devueltos o como argumentos para otros invocables, como se muestra en el ejemplo siguiente:

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

Se pueden crear instancias de ambas según una definición parametrizada por tipo; por ejemplo, la función Powparametrizada por tipo anterior, y se pueden aplicar parcialmente como se ha hecho en la instrucción return del ejemplo.

Características de la operación

Además de la información sobre el tipo de entrada y salida, el tipo de operación contiene información sobre las características de una operación. Esta información, por ejemplo, describe qué functores admite la operación. Además, la representación interna también contiene la información pertinente de optimización que infiere el compilador.

Las características de una operación son un conjunto de etiquetas predefinidas e integradas. Se expresan en forma de expresión especial que forma parte de la firma del tipo. La expresión consta de uno de los conjuntos predefinidos de etiquetas o de una combinación de expresiones de características a través de un operador binario compatible.

Hay dos métricas predefinidas, Adj y Ctl.

  • Adj es el conjunto que contiene una sola etiqueta que indica que una operación se puede adjuntar, lo que significa que admite el functor Adjoint y la transformación cuántica aplicada se puede "deshacer", es decir, se puede invertir.
  • Ctl es el conjunto que contiene una sola etiqueta que indica que una operación es controlable, lo que significa que admite el functor Controlled y su ejecución se puede condicionar al estado de otros cúbits.

Los dos operadores que se admiten como parte de las expresiones de características son la unión de conjuntos + y la intersección de conjuntos *. En EBNF,

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

Tal y como se esperaría, * tiene más prioridad que +, y ambos son asociativos por la izquierda. El tipo de una operación unitaria, por ejemplo, se expresa como <TIn> => <TOut> is Adj + Ctl, donde <TIn> se debe reemplazar por el tipo del argumento de operación y <TOut> se puede reemplazar por el tipo del valor devuelto.

Nota

Indicar las características de una operación con este formato tiene dos ventajas principales: uno, se pueden introducir nuevas etiquetas sin tener exponencialmente muchas palabras clave de lenguaje para todas las combinaciones de etiquetas. Quizás lo más importante es que el uso de expresiones para indicar las características de una operación también permite parametrizaciones sobre las características de la operación en el futuro.