Chapter 4. Statements

This chapter offers an overview of the statements defined in Q#. A statement is a program instruction that describes an action to be performed. Q# is an imperative programming language, so its operations and functions are sequences of statements.

I’ll start with a review of “classical” statements: variable declaration and reassignment, conditional execution, loops, calling other routines, and halting routine execution. Most other programming languages have some variants of these statements, and you’re probably familiar with them conceptually, so this part focuses mostly on the syntax and any language-specific nuances.

The second part of the chapter covers statements specific to quantum programs: qubit allocation, a quantum variant of a conditional loop, and a quantum-specific flow-control construct called conjugation.

Example: Calculate Euler’s Totient Function

Let’s take a look at Example 4-1, a Q# program that calculates Euler’s totient function of the given integer n. The value of this function equals the number of positive numbers less than or equal to n that are coprime to it—that is, they don’t have common divisors greater than 1. This example implements the most straightforward approach to computing this function: it iterates over all numbers from 1 to n and checks whether each of them is coprime to n by calculating their greatest common divisor (GCD). This code is designed to showcase the main “classical” statements we will be considering later in this chapter.

Example 4-1. Calculating Euler’s totient function in Q#
namespace EulersTotientFunction {
    open Microsoft.Quantum.Intrinsic; // Message

    /// # Summary
    /// Calculates Euler's totient function.
    function TotientFunction(n : Int) : Int {
        // Conditional statement checks input validity.
        if n <= 0 {
            // Fail statement throws an exception.
            fail "The argument n should be positive";
        }
        // Declare mutable variable to count coprimes.
        mutable nCoprimes = 0;
        // For loop runs through numbers from 1 to n.
        for i in 1 .. n {
            // Declare mutable variables to use when
            // computing GCD of i and n.
            mutable (x, y) = (n, i);
            // While loop runs while y is positive.
            while y > 0 {
                // Reassign both mutable variables.
                set (x, y) = (y, x % y);
            }
            // At this point x = GCD(i, n).
            // Conditional statement checks whether
            // the current number is coprime with n.
            if x == 1 {
                // Evaluate-and-reassign statement.
                set nCoprimes += 1;
            }
        }
        // Return statement.
        return nCoprimes;
    }


    @EntryPoint()
    function PrintEulersTotientFunction() : Unit {
        // Declare immutable variable - the argument
        // to use when computing the function.
        let n = 4;
        // Call statement prints the function value.
        Message($"φ({n}) = {TotientFunction(n)}");
    }
}

Indentation in Q# is not significant: blocks are marked explicitly using curly brackets { and }, similarly to C++ and C#, rather than using indentation, like in Python. However, the Q# style guide recommends indenting blocks to improve code readability.

To compute the value of the function for other inputs, you’ll need to modify this program to define a different value of n. Q# doesn’t support user input during program runtime. In Chapter 6, I’ll show you how to define programs that take input from the user upon their invocation, and how to pass input to them.

The part of the for loop body that calculates the greatest common divisor of two integers could be replaced with a single call to the GreatestCommonDivisorI library function from the Microsoft.Quantum.Math namespace (see Chapter 7). However, I chose to spell it out in order to include more statements in this example.

Let’s take a closer look at the various statements used in this program.

Working with Variables

Q# offers multiple statements for declaring and modifying Q# variables. Before we dive into the statements themselves, I’ll make some general notes about variable handling in Q# that will be important for the rest of the chapter.

Variable Scope

In general the scope of a variable defined within a statement block, such as a body of a callable or a loop body, is that statement block. Variables defined in an outer block will be visible in an inner block, but not vice versa. You cannot define a variable with a name that has already been defined earlier in the block or in an outer block; Q# prohibits this practice, which is known as variable shadowing.

All Q# variables are local; an attempt to define a global variable will yield a compilation error. You might have noticed that our discussion of top-level Q# elements in Chapter 1 did not include global variable declarations; this is why.

Several statements have slightly different rules that define visibility of the variables defined in them. In these cases, I’ll make a separate note when discussing the corresponding statement.

Mutable and Immutable Variables

Q# has two types of variables: mutable and immutable. An immutable variable is effectively a constant—it’s a variable that is always bound to the same value and cannot be re-bound to a different one. A mutable variable is a “normal” variable that can take on different values during the program execution.

Q# differentiates between mutable and immutable variables on the language level to simplify the analysis of the program behavior, so you have to declare whether the variable will be reassigned later up front. In Chapter 5, we’ll see how variable mutability affects code generation performed by the Q# compiler.

Declaring Immutable Variables: let Statements

A let statement allows you to declare an immutable variable (that is, a constant) and to bind it to its value.

The let statement consists of the following parts:

  • A let keyword

  • A variable name or a tuple of names

  • An equal sign

  • The expression that is being bound to the variable

  • A semicolon

let n = 10;
let doubleArray = [1., 2., 3.];
let name = "Mariia";
let gate = H;

The type of the variable assigned can be any type supported by Q#. Note that the let statement doesn’t specify the type of the variable it defines explicitly. The type of the variable is inferred from the expression on the right side of the equal sign.

We will discuss assigning tuples separately a bit later in the chapter.

Declaring Mutable Variables: mutable Statements

A mutable statement allows you to declare a mutable variable (a variable that can take different values during the program execution) and bind it to its initial value.

The mutable statement consists of the following parts:

  • A mutable keyword

  • A variable name or a tuple of names

  • An equal sign

  • The expression that is being bound to the variable

  • A semicolon

mutable m = 10;
mutable intArray = [1, 2, 3];

As with the let statement, the type of the variable defined using the mutable statement is inferred from the expression on the right side, and can be any type supported by Q#. The difference between the mutable and let statements comes in play with the next statement we’ll look at, which allows you to reassign mutable variables.

Reassigning Mutable Variables: set Statements

A set statement allows you to reassign the value of a mutable variable to a different value.

The set statement consists of the following parts:

  • A set keyword

  • A variable name or a tuple of names

  • An equal sign

  • The expression that is being bound to the variable

  • A semicolon

set m = m + 32;
set intArray = intArray + [4];

You cannot use the set statement with an immutable variable defined using a let statement; an attempt to do this will cause a compilation error.

Note that since Q# is a statically typed language, a set statement cannot change the type of the variable that was defined by the mutable statement. An attempt to assign a value to a variable of a different type without an explicit type cast will cause a compilation error.

Evaluate-and-reassign statements are shorthand for a special type of set statements. If the expression on the right side of the equal sign in a set statement is an application of a binary operator to the variable on the left side and another expression, you can use an evaluate-and-reassign statement to write it more concisely.

Evaluate-and-reassign statements are defined for arithmetic and bitwise binary operators, as well as string and array concatenation operators and copy-and-update expressions for arrays and UDTs. The following code snippet shows several pairs of equivalent set statements, in which the second variant is shorthand for the first variant:

// Increment a number by 32.
set m = m + 32;
set m += 32;

// Multiply a number by 2 using bit shift.
set m = m <<< 1;
set m <<<= 1;

// Append an element to the current array.
set intArray = intArray + [4];
set intArray += [4];

// Replace the first element of an array with 5
// using a copy-and-update expression.
set intArray = intArray w/ 0 <- 5;
set intArray w/= 0 <- 5;

// Replace an element of a UDT defined as
// newtype NamedPair = (Fst : Int, Snd : Bool);
// using a copy-and-update expression.
set np = np w/ Fst <- 3;
set np w/= Fst <- 3;

In order to expand an evaluate-and-reassign statement into a full set statement, the Q# compiler uses the mutable variable on the left side of the equal sign as the left-hand argument of the binary operator, and the expression on the right side of the equal sign as the right-hand argument of the binary operator. This means, for example, that you cannot use the mutable variable in an evaluate-and-reassign statement as the divisor in the division expression or the shift amount for the left-shift operator, since they are right-hand arguments of the corresponding operators.

Assigning Tuples: Tuple Deconstruction

Before moving on to different statements, let’s take a closer look at assigning tuples. let, mutable, and set statements can all be applied to tuples as well as other data types. Conveniently, they are not limited to dealing with the tuple being assigned as a whole; tuple deconstruction allows you to deconstruct the tuple into its components and assign them to different variables separately.

Let’s look at some examples that illustrate tuple deconstruction and its applications:

// Access tuple components separately.
let tuple = (5, false);
let (first, second) = tuple;
// first = 5, second = false

// Access tuple components that are tuples.
let nestedTuple = (42, (PauliX, 2.0));
let (n, pair) = nestedTuple;
// n = 42, pair = (PauliX, 2.0)

// Access nested tuple components.
let (n, (pauli, angle)) = nestedTuple;
// n = 42, pauli = PauliX, angle = 2.0

// Access some, but not all, components.
let (_, (pauli, angle)) = nestedTuple;

// Update two variables simultaneously.
set (x, y) = (y, x % y);

// Use library function that returns a tuple.
let (u, v) = ExtendedGreatestCommonDivisorI(a, b);

Note the fourth snippet that shows that you don’t need to assign all components of the tuple to some variables when using tuple decomposition; you can use underscore (_) in lieu of any tuple components you don’t need.

Tuple deconstruction is the only way to access unnamed tuple elements individually. Besides, it can be very convenient when working with more complicated code, since it allows you to use functions or operations that return tuples of values, arrays of tuples, and so on. The last snippet shows accessing the return value of a library function that computes the greatest common divisor of two integers a and b, represents it as a linear combination a ⋅ u + b ⋅ v = GCD(a, b), and returns a tuple (u, v).

Conditional Execution: if Statements

A conditional statement allows you to execute different statements depending on the values of one or several expressions. Q# offers a single if statement that implements three variants of conditional execution.

The if statement consists of the following parts:

  • An if keyword

  • A Boolean expression

  • A block of statements that are executed if the expression evaluates to true

For example:

if n <= 0 {
    fail "The argument n should be positive";
}

The if-else statement expands the if statement, following it with an else keyword and a block of statements that are executed if the expression in the if statement evaluates to false:

if n % 2 == 0 {
    Message("The number is even");
}
else {
    Message("The number is odd");
}

Finally, the if-elif-else statement allows you to check several conditions in a row, executing the first block for which its matching condition evaluates to true, and executing the optional else block if all conditions evaluate to false.

The following code snippet implements the decision-making fragment of the FooBar task, which, given an integer number, prints “Foo” if it is divisible by 3, “Bar” if it is divisible by 5, “Foobar” if it is divisible by both, and the number itself if it is divisible by neither 3 nor 5:

if i % 15 == 0 {
    Message("Foobar");
}
elif i % 3 == 0 {
    Message("Foo");
}
elif i % 5 == 0 {
    Message("Bar");
}
else {
    Message($"{i}");
}

Note that the statement blocks in each scenario must be enclosed in curly brackets, even if some of the blocks contain only a single statement. Q# doesn’t allow you to omit curly brackets around the statement blocks; you’ll see other examples of this later in this chapter. Enclosing the conditional expressions in round brackets is optional, though.

Remember that Q# doesn’t support implicit type casts; all expressions used after if and elif keywords must be Boolean.

Loops

A loop allows you to execute a fragment of code repeatedly. Q# offers three types of loops; we will start with the first two that were featured in Example 4-1 and discuss the third one later in the chapter.

Iterate Over a Sequence: for Loops

A for loop allows you to iterate over the elements of a sequence—a range or an array—and to perform certain actions for each element. (You cannot iterate over a tuple, since a tuple represents a group of items of different types rather than a sequence of items of the same type.)

The for statement consists of the following parts:

  • A for keyword

  • A variable or a tuple of variables

  • An in keyword

  • An expression that evaluates to a range or an array

  • The block of statements that make up the loop body

Let’s take a look at several examples of for loops and the variations in their functionality.

The simplest variant of the for loop iterates over a range of integers. The loop variable is an integer that takes on each value in the range in turn. Here, for example, is the Q# code for calculating the factorial of the number n. The for loop is used to multiply the mutable variable fact by each of the integers from 2 to n, inclusive:

mutable fact = 1;
for i in 2 .. n {
    set fact *= i;
}

If you don’t need to use the loop variable in the loop (that is, you just need to execute the loop body a certain number of times), you can replace the loop variable with an underscore (_). For example, the following code snippet calculates the nth power of 2 by multiplying the mutable variable power by 2 n times.

mutable power = 1;
for _ in 1 .. n {
    set power *= 2;
}

The second variant of the for loop iterates over the elements of an array. In this case the loop variable is of the same type as the array elements, which can be any of the types supported by Q#, from integers to data structures or even callables. This code snippet applies the Hadamard gate to each of the qubits in the array:

use qs = Qubit[n];
for q in qs {
    H(q);
}

For a more interesting example, let’s apply Hadamard gates followed by different single-qubit gates with different parameters to different qubits in the array. Here, the type of the array in the for loop is ((Double, Qubit) => Unit, Double, Qubit)[], and each of its elements is a tuple of three values: a gate of type (Double, Qubit) => Unit that acts on a qubit and takes an extra floating-point parameter (see Chapter 5), a floating-point parameter to use, and a qubit to apply this gate to. Note that using tuple deconstruction when defining the loop variable allows you to define individual variables in the loop header instead of defining a tuple loop variable and deconstructing it in the loop body:

let gates = [Rx, Ry, Rz];
let angles = [1., 2., 3.];
use qs = Qubit[3];
let array = Zipped3(gates, angles, qs);
for (gate, angle, qubit) in array {
    H(qubit);
    gate(angle, qubit);
}
Note

This example uses a library function Zipped3, which “zips” three arrays into an array of tuples that combine respective elements of each array. Microsoft.Quantum.Arrays namespace contains a lot of very convenient functions for working with arrays (see Chapter 7).

You can replace the loop variable with an underscore when iterating over the array as well, but it is less common than when iterating over a range. In this variant you’re likely to use the array elements in the loop body, rather than just use the length of the array to define the number of loop iterations. However, if your code iterates over an array of tuples, it can be convenient to replace some of the variables with underscores in tuple deconstruction if your loop body needs only part of the information stored in each tuple. For example, you could split the loop from the previous code snippet into two, applying the Hadamard gates in the first loop and the rotation gates in the second one. In this case the first loop doesn’t need the information about the rotation gates you’ll use in the second loop, so it can be ignored:

for (_, _, qubit) in array {
    H(qubit);
}
for (gate, angle, qubit) in array {
    gate(angle, qubit);
}

Loop variables are immutable; they are bound to each element of the sequence for the matching iteration, and go out of scope at the end of the loop body.

Q# for loops don’t support transfer of control statements such as break or continue. If you anticipate the need to stop iteration early depending on some condition, use the while loop or the repeat-until loop that we’ll cover later in this chapter. Alternatively, if your for loop doesn’t involve allocated qubits, you can use a return statement to leave the loop and the operation/function in which it is defined.

Classical Conditional Loop: while Loops

A while loop allows you to evaluate an expression before each iteration and execute the loop body only if that expression evaluates to true. Note that in Q# this loop is purely classical: you can only use it within functions (recall that Q# functions can contain only classical computations). For similar functionality in operations, use the repeat-until loop we’ll discuss later in this chapter.

The while statement consists of the following parts:

  • A while keyword

  • A Boolean expression

  • A block of statements that are executed as long as the expression evaluates to true

For example, the following code snippet implements a function that finds the index of a negative element in the given array, and it returns that index or -1 if all array elements are nonnegative:

function FirstNegativeIndex(a : Int[]) : Int {
    mutable ind = 0;
    while ind < Length(a) and a[ind] >= 0 {
        set ind += 1;
    }
    return ind == Length(a) ? -1 | ind;
}

Note that, unlike in the repeat-until loop you’ll see later, the condition of the while loop must be defined using variables declared outside the loop body that are updated in the loop body.

Call an Operation or a Function: Call Statements

A call statement allows you to call an operation or a function that returns a Unit value. (As you learned in Chapter 3, calling an operation or a function that returns any other type of value is a kind of expression.) For example:

// Calling an operation.
X(q);
// Calling a built-in function.
Message("Hello, quantum world!");
Note

Most call statements will involve calling operations. In most cases, functions are used to perform classical computations and to return the result of these computations, and thus can be used only as expressions. The few functions that return a Unit type and thus can be called as statements are special functions that rely on their side effects. The examples include the Message function you’ve seen in Example 4-1 and the debugging functions you’ll see in Chapter 8.

We will discuss the behavior of operations and functions and calling operations in more detail in Chapter 5.

Stop Execution: return and fail Statements

Q# offers two ways to stop execution of the current operation/function, depending on whether it completed successfully or a fatal error occurred.

Note

Q# does not support other types of transfer-of-control statements offered by many general-purpose languages, such as break, continue, or goto.

Finish Execution: return Statements

A return statement signals successful completion of the current operation/function. If the signature of the callable specifies that it has a return value of type other than Unit, a return statement also returns the result of the computation. This statement stops execution of the current operation/function and returns control to the caller. If the current callable was the entry point of Q# code, the return statement returns control to the classical host program or the execution environment.

A return statement consists of the following parts:

  • A return keyword

  • An expression of the type that matches the return type of the current operation/function

  • A semicolon

For example:

function FortyTwo() : Int {
    return 42;
}

If the callable return type is Unit, the return statement has to return the value ():

/// # Summary
/// Prepares the qubit in the basis state given
/// by the measurement result in computational
/// basis. The qubit starts in the |0⟩ state.
operation PrepareBasisState(
    q : Qubit,
    state : Result
) : Unit {
    if state == Zero {
        // The qubit is already in the Zero state.
        return ();
    }
    X(q);
}

A callable can have multiple execution paths; each must end in a return statement, except for the path that ends at the end of a callable that returns Unit, as you can see in the preceding example.

Q# imposes some restrictions on the use of return statements. You cannot use a return statement in the middle of the block that allocated qubits, but you can use it as the last statement of such a block. As long as no allocated qubits are involved, you can use a return statement at any point in the code, though you might get a “This statement will never be executed” warning for any statements after the return statement.

Throw an Exception: fail Statements

A fail statement signals a fatal error from which the program cannot recover. This statement halts Q# program execution completely and returns control to the classical host program or the execution environment, depending on how you run this Q# program.

A fail statement consists of the following parts:

  • A fail keyword

  • A string—the message that will accompany the exception thrown by the statement

  • A semicolon

The error message can be any expression of type String, including interpolated strings:

if n <= 0 {
    fail "The argument n should be positive";
}

You can use the fail statement at any point inside a function or an operation, including the places where you cannot use a return statement.

Note

fail statements are broadly used by Q# library operations and functions to ensure the constraints on their expected inputs, and in the implementation of unit tests that cover Q# libraries. They also power a lot of functions from the Microsoft.Quantum.Diagnostics namespace (please see Chapter 8).

Example: Prepare a Quantum State

Now let’s consider Example 4-2, a Q# program that prepares a quantum state 1 3 (|00⟩ + |10⟩ + |11⟩). It showcases all the remaining statements we will cover in this chapter, including all “quantum” statements.

Example 4-2. Preparing superposition state 1 3 (|00 + |10 + |11)
namespace StatePreparation {
    open Microsoft.Quantum.Canon;       // ApplyToEach
    open Microsoft.Quantum.Diagnostics; // DumpRegister
    open Microsoft.Quantum.Intrinsic;   // Message
    open Microsoft.Quantum.Measurement; // MResetZ

    @EntryPoint()
    /// # Summary
    /// Prepares state (|00⟩ + |10⟩ + |11⟩) / √3.
    operation PrepareSuperpositionState() : Unit {
        // Use statement allocates the qubits.
        use (qs, aux) = (Qubit[2], Qubit());
        // Repeat-until loop runs until the loop body
        // succeeds at preparing the right state.
        repeat {
            // Call statement applies H gates to qubits
            // to prep (|00⟩ + |01⟩ + |10⟩ + |11⟩) / 2.
            ApplyToEach(H, qs);
            // Conjugation statement marks the basis
            // state we need to discard (|10⟩)
            // with |1⟩ in qubit aux.
            within {
                // Call statement applies X gate
                // to the second qubit.
                X(qs[1]);
            } apply {
                // Call statement applies controlled
                // variant of X gate to the qubits.
                Controlled X(qs, aux);
            }
            // Variable assignment statement measures
            // qubit aux and stores the result.
            let res = MResetZ(aux);
        } until res == Zero
        fixup {
            // Call statement resets qubits to |0⟩.
            ResetAll(qs);
        }
        // Call statement prints the qubits' state.
        DumpRegister((), qs);
        // Call statement resets qubits to |0⟩.
        ResetAll(qs);
    }
}
Note

As in Example 4-1, this program can be implemented more efficiently using the ControlledOnBitString library function from the Microsoft.Quantum.Canon namespace to replace the within-apply statement (see Chapter 7). However, it is spelled out here in order to include more statements in this example.

Let’s take a closer look at the new Q# statements you see in this program.

Allocate Qubits: use and borrow Statements

The key “quantum” statement in Q# is qubit allocation. Any Q# program always starts with no qubits and has to allocate and release any qubits it needs.

There are two statements you can use to allocate qubits: the use statement allocates fresh qubits in the |0⟩ state, and the borrow statement allocates qubits that are in some other state. These statements can appear only in operations, never in functions, due to their inherently quantum nature. The two statements have identical syntax. The vast majority of qubit allocations in Q# programs rely on the use statement, so in this section I’ll focus mostly on that, and note the subtleties of the borrow statement afterward.

The use statement consists of the following parts:

  • A use keyword

  • A variable name or a tuple of names

  • An equal sign

  • The initializer that is being bound to the variable

  • A semicolon or an optional block of statements

The right side of the equal sign can have one of the limited set of initializers: Qubit() to allocate a single qubit, Qubit[n] to allocate an array of n qubits, or tuples thereof to allocate several qubits and/or qubit arrays in a single statement:

use q = Qubit();
...

use qs = Qubit[n];
...

use (input, output) = (Qubit[3], Qubit()) {
    // ...
}

The optional block of statements after the use keyword defines the scope of the allocated qubits: the section of the program in which they will be available. Q# doesn’t have a separate “release” statement to accompany the use and borrow statements. Instead, the allocated qubits are released automatically at the end of the block in which they were allocated (in case of the simple form of the use statement) or at the end of the block that is part of the use statement, if such a block is present.

The qubits allocated with the use statement must be returned to the |0⟩ state or measured before the release.

The compiler does not enforce this requirement. The qubits can be returned to the |0⟩ state without measurement using uncomputation: undoing the part of the computation in which they were involved. This means that verifying their state upon release would effectively require the compiler to simulate the program during its compilation, which is not possible for programs larger than a certain (rather small) threshold. However, quantum simulators offer this verification at runtime as part of their functionality; if your code performs neither uncomputation nor measurement before returning the qubits, running it on a quantum simulator will throw a runtime error (see Chapter 6).

The borrow statement allocates qubits in an arbitrary unknown state and expects your program to return them to the same state before releasing them:

borrow q = Qubit();
// ...
// Remember to return q to its original state!

Under the hood, the borrow statement literally borrows temporarily unused qubits from other parts of the program. These qubits are not actively being used by the code that allocated them originally, so the current operation can borrow them for a bit and use them, as long as it returns the borrowed qubits to their starting state before releasing them. This means that, for example, you cannot measure the borrowed qubits—this would destroy the information about their original state and ruin the computation from which they were borrowed.

Note

The scenarios in which borrowed qubits can be useful are pretty advanced, and out of scope for this book. You can see the paper “Factoring using 2n+2 qubits with Toffoli based modular multiplication” by Thomas Häner et al. for an example of an algorithm that uses borrowed qubits to implement multicontrolled CNOT gates with few qubits.

As discussed in Chapter 2, you can define variables of Qubit data type using let and mutable statements to copy allocated qubits to new variables. However, this does not create or allocate new qubits, nor does it violate the no-cloning theorem. Instead, copying a qubit to a different variable creates another copy of the reference to the same qubit:

// Allocate a qubit in an unknown state.
borrow q = Qubit();
// Create another variable pointing to it.
let qCopy = q;
// Measure the copied qubit and reset it to |0⟩.
let res = MResetZ(qCopy);
// q points to the same qubit in the |0⟩ state!

Copying qubit variables is typically used to combine qubits from several sources—for example, combining qubits passed as an argument to an operation and auxiliary qubits allocated within that operation into a single array to be used as an argument for another operation.

Quantum Conditional Loops: repeat-until Loops

A repeat-until loop allows you to repeat iterations until a certain condition is satisfied. This statement can appear only in operations, and never in functions, since it is designed to work with quantum logic and conditions based on measurement results. For similar functionality in functions, use the while loop discussed earlier in this chapter.

The repeat-until statement consists of the following parts:

  • A repeat keyword

  • A block of statements that represents the loop body

  • An until keyword

  • A Boolean expression

  • A semicolon or an optional fixup keyword followed by a block of statements that is executed after each iteration if the expression evaluated to false and the loop is going to restart

Here is a short example of using a repeat-until loop to prepare the |1⟩ state without using an X gate. In it, the loop body puts a qubit in a superposition using the Hadamard gate and measures it; if the measurement yielded Zero, the loop repeats, but if it yielded One, we know that the qubit ended up in the |1⟩ state:

use q = Qubit();
repeat {
    H(q);
    let res = M(q);
} until res == One;

In Example 4-2, you saw a more interesting example of a repeat-until loop in action:

  • The loop body attempts to prepare the required state by preparing a different state using an extra qubit and measuring that extra qubit. This will prepare the required state with 75% probability and a different, incorrect state with 25% probability.

  • The exit condition res == Zero is based on the outcome of the measurement, which tells us whether we got the required state or a different one.

  • If we got a different state, the loop repeats itself after performing the fixup block, which resets the qubits to their starting state so that the loop body can start from a clean slate (ResetAll(qs)).

All parts of the loop form a single scope, so any variables defined in the loop body are visible to the expression that defines the exit condition and to the fixup block for the same iteration. This means, for example, that the exit condition can be defined using variables declared in the loop body, rather than mutable variables.

A repeat-until loop can also be used to implement a purely classical computation, as long as it happens in an operation and not in a function. But usually it’s better to separate a classical computation that requires conditional iterating into a function, and to use a while loop instead.

Conjugation: within-apply Statements

The conjugation statement is also known as a within-apply statement. This statement can appear only in operations, and never in functions, due to its inherently quantum nature. It implements the pattern of applying the unitary transformations UVU, where U and V are unitary transformations, and U is an adjoint of the transformation U: a transformation that undoes the effects of U. In terms of Q# code, the conjugation statement applies two blocks of code, U and V, followed by an automatically generated adjoint of the first block U.

Unlike other language statements, conjugation is not fundamentally necessary to a quantum programming language; in fact, it was introduced to Q# only in version 0.9, almost two years after the first release! However, conjugations are ubiquitous in quantum programs, expressing concepts such as uncomputation, which returns temporarily allocated qubits to their starting states before releasing them by undoing the part of the computation in which they were involved.

Seeing how often this code pattern occurred in Q# programs, it made sense to add a special statement to the language to make writing them more convenient and less error-prone. Indeed, spelling out the adjoint transformation U manually means either forfeiting the advantages of automatic adjoint generation provided by the Q# compiler (and potentially introducing errors in the process) or having to define a separate operation for the transformation U and calling its adjoint (leading to bulkier and harder-to-read code). I will talk more about adjoints in Chapter 5.

The conjugation statement consists of the following parts:

  • A within keyword

  • The first block of code (transformation U)

  • An apply keyword

  • The second block of code (transformation V)

The following code snippet shows two equivalent fragments of code, both of which implement a controlled-on-zero variant of the X gate—a gate that flips the state of the target qubit if both control qubits are in the |0⟩ state:

within {
    ApplyToEachA(X, qs);
} apply {
    Controlled X(qs, aux);
}

ApplyToEachA(X, qs);
Controlled X(qs, aux);
Adjoint ApplyToEachA(X, qs);

This example is too small to illustrate the advantages of using a conjugation statement instead of writing out the adjoint of the first block manually. But, as you can imagine, real-world quantum programs tend to have much more complex code in both within and apply blocks, so a conjugation statement can be really handy.

The code in the within block of a conjugation statement must be adjointable, i.e., it must be written in a way that allows the Q# compiler to generate its adjoint. (Note the use of library operation ApplyToEachA in the last code snippet instead of ApplyToEach; the only difference between these two operations is that ApplyToEachA has an adjoint variant defined and ApplyToEach does not.) If the within block includes any statements that don’t have an adjoint specialization, the compiler will throw an error pointing to them and explaining why the adjoint cannot be generated.

Conclusion

In this chapter, you’ve learned everything about Q# statements, both classical and quantum ones.

In the next chapter, we will discuss Q# operations and functions, which will wrap up the first part of the book.

Get Q# Pocket Guide now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.