Skip to content

Differences from the previous QDK

Mariia Mykhailova edited this page Apr 11, 2024 · 10 revisions

Language Syntax Updates

Expression-based

As part of developing the new compiler, the Q# language has been updated to be expression-based instead of statement-based. This allows for new use of existing syntax, such as embedding an if-expression into another expression:

let x = if check { 0 } else { 1 };

Implicit return

The previous example also takes advantage of using statements without a trailing semicolon at the end of a block to return a value from that block. This pattern can be used instead of explicit return-expressions in a callable:

function ReturnsThree() : Int {
    return 3;
}

function AlsoReturnsThree() : Int {
    3
}

Block-expressions

Block-expressions are now supported that can organize multiple lines, scope variables, and return a value:

let flip = {
    use q = Qubit();
    H(q);
    if M(q) == One {
        X(q);
        "Heads"
    } else {
        "Tails"
    }
};

Items as Statements

Items like newtype, operation, function and even open can now appear as statements within a local scope. This allows for definition of local helper types and callables, as well as scoped includes. For example, a local helper function can be defined just before it is needed:

function ShowDecrement() : Unit {
    open Microsoft.Quantum.Arrays;
    let before = [1, 2, 3];

    function SubOne(in : Int) : Int {
        in - 1
    }

    let after = Mapped(SubOne, before);
    // after will be [0, 1, 2]
}

Both the function SubOne and the open of the Microsoft.Quantum.Arrays namespace are scoped to the ShowDecrement function and do not affect code outside of that.

Name Shadowing

The new QDK allows for shadowing of resolved names where this was disallowed in the previous QDK. This allows for easier reuse of code that includes variable or callable names without the need for a mutable variable:

function Shadowing(input : Int) : Double {
    let input = 2 * input;
    let output = Calculate(input);
    let output = IntAsDouble(output);
    return output;
}

Explicit Types for Local Variables

Local variables can now be explicitly typed using the same syntax as types for callable argument declarations:

let x : Int[] = [];

Explicit types are not required but can sometimes be helpful when resolving type ambiguity for functions that accept generics, such as Length. The previous pattern for handling this, adding concrete types to a generic function invocation with syntax like Length<Int[]>([]);, is no longer supported.

Implicit Namespace Prelude

Name resolution now incorporates an implicit prelude which is a set of namespaces that are treated as if they had been opened as long as no other resolved names supersede them. The namespaces that are treated this way are Microsoft.Quantum.Core, Microsoft.Quantum.Canon, Microsoft.Quantum.Intrinsic, and Microsoft.Quantum.Measurement. These do not need to be explicitly opened except when using aliases or otherwise differentiating from potential conflicts.

Standard Library

The Q# standard library is now hosted in the same repository as the compiler and runtime, and can be found in the top-level library folder. Not all functionality and features have been migrating from the existing Q# libraries, found at https://github.com/microsoft/QuantumLibraries. If an item from the previous library is needed for a program, that item and any dependencies can be copied into the source program. If any library functionality is critical to a workflow and should be considered for inclusion in the new standard library, please file an issue with the details.

Simulation

Sparse Simulation by Default

The new QDK uses a built-in sparse state quantum simulator as the default target for local simulation. This simulator is written in Rust and comes from the QIR Runner repository, allowing it to compile to WASM and run across a variety of environments. Currently this is the only simulation backend available in the QDK, though other backends are under consideration for future integration. For more information on the sparse simulation design, see Testing large quantum algorithms using sparse simulation.

Stricter Checks on Qubit Release

The previous QDK had historically relaxed the requirement that qubits be in the ground or |0⟩ state before being released at the end of their scope, such that a qubit that has been measured and not operated on any further was also safe to release since the simulator would automatically reset the qubit. However, this caused confusion when the same program was run on real quantum hardware where such an automatic reset is not present and qubits may be reused in an unexpected state. For the new QDK we've returned to the stricter behavior of enforcing qubits are in the ground state on release. This helps algorithm authors verify the behavior of their algorithm for correctness in preparation for running on hardware.

Decomposed Multi-Controlled Gates

The new QDK now uses decomposition strategies for multi-controlled gates. While this doesn't take advantage of shortcuts available to simulation where multi-controlled gates are easily implemented, it does more closely match the behavior of physical quantum systems. This means performing a gate with a large number of control qubits will incur additional qubit allocations and preparation gates the same way it would when compiled for execution on hardware. For more details on the decomposition algorithms used, see the implementation in the standard library.

QIR Generation

The new QDK produces QIR by generating the textual representation of LLVM (.ll) instead of using bitcode (.bc). Most targets and tools that accept bitcode can also parse textual LLVM, including tools like PyQIR and QIR Runner.

The new QDK is currently limited to QIR generation for programs compatible with the QIR Base Profile. When Base Profile compilation is configured, the compiler and/or VSCode extension will produce errors for patterns that are not compatible with the target. The compiler can also conditionally skip compilation of items that have attributes indicating they are specific to particular compilation targets:

@Config(Unrestricted)
function ResultAsBool(input : Result) : Bool {
    input == One
}

Adaptive QIR generation is on the roadmap but not yet implemented.