Q# programming language style guide
General conventions
The conventions suggested in this guide are intended to help make programs and libraries written in Q# programming language easier to read and understand.
Tip
For detailed user guidance for Q#, see the Q# quantum programming language user guide.
Guidance
We suggest:
- Never disregard a convention unless you’re doing so intentionally in order to provide more readable and understandable code for your users.
Naming conventions
In offering the Quantum Development Kit, we strive for function and operation names that help quantum developers write programs that are easy to read and that minimize surprise. An important part of that is that when we choose names for functions, operations, and types, we are establishing the vocabulary that programmers use to express quantum concepts; with our choices, we either help or hinder them in their effort to clearly communicate. This places a responsibility on us to make sure that the names we introduce offer clarity rather than obscurity. In this section, we detail how we meet this obligation in terms of explicit guidance that helps us do the best by the Q# development community.
Operations and functions
One of the first things that a name should establish is whether a given symbol represents a function or an operation. The difference between functions and operations is critical to understanding how a block of code behaves. To communicate the distinction between functions and operations to users, we rely on that Q# models quantum operations through the use of side effects. That is, an operation does something.
By contrast, functions describe the mathematical relationships between data.
The expression Sin(PI() / 2.0)
is 1.0
, and implies nothing about the state of a program or its qubits.
Summarizing, operations do things while functions are things. This distinction suggests that we name operations as verbs and functions as nouns.
Note
When a user-defined type is declared, a new function that constructs instances of that type is implicitly defined at the same time. From that perspective, user-defined types should be named as nouns so that both the type itself and the constructor function have consistent names.
Where reasonable, ensure that operation names begin with verbs that clearly indicate the effect taken by the operation. For example:
MeasureInteger
EstimateEnergy
SampleInt
One case that deserves special mention is when an operation takes another operation as input and calls it.
In such cases, the action taken by the input operation is not clear when the outer operation is defined, such that the right verb is not immediately clear.
We recommend the verb Apply
, as in ApplyIf
, ApplyToEach
, and ApplyToFirst
.
Other verbs may be useful in this case as well, as in IterateThroughCartesianPower
.
Verb | Expected effect |
---|---|
Apply | An operation provided as input is called |
Assert | A hypothesis about the outcome of a possible quantum measurement is checked by a simulator |
Estimate | A classical value is returned, representing an estimate drawn from one or more measurements |
Measure | A quantum measurement is performed, and its result is returned to the user |
Prepare | A given register of qubits is initialized into a particular state |
Sample | A classical value is returned at random from some distribution |
For functions, we suggest avoiding the use of verbs in favor of common nouns (see guidance on proper nouns or adjectives:
ConstantArray
Head
LookupFunction
In particular, in almost all cases, we suggest using past participles where appropriate to indicate that a function name is strongly connected to an action or side effect elsewhere in a quantum program.
For example, ControlledOnInt
uses the part participle form of the verb "control" to indicate that the function acts as an adjective to modify its argument.
This name has the additional benefit of matching the semantics of the built-in Controlled
functor, as discussed later in this article.
Similarly, agent nouns can be used to construct function and UDT names from operation names, as in the case of the name Encoder
for a UDT that is strongly associated with Encode
.
We suggest:
- Use verbs for operation names.
- Use nouns or adjectives for function names.
- Use nouns for user-defined types and attributes.
- For all callable names, use
CamelCase
in strong preference topascalCase
,snake_case
, orANGRY_CASE
. In particular, ensure that callable names start with uppercase letters. - For all local variables, use
pascalCase
in strong preference toCamelCase
,snake_case
, orANGRY_CASE
. In particular, ensure that local variables start with lowercase letters. - Avoid the use of underscores
_
in function and operation names; where additional levels of hierarchy are needed, use namespaces and namespace aliases.
Entry points
Note
Entry points are only required for standalone Q# programs. For more information, see Ways to run a Q# program
When defining an entry point into a Q# program, the Q# compiler recognizes the @EntryPoint()
attribute rather than requiring that entry points have a particular name, for example, main
, Main
, or __main__
.
That is, from the perspective of a Q# developer, entry points are ordinary operations annotated with @EntryPoint()
.
We suggest naming entry points to reflect the use of the @EntryPoint()
attribute by using the advice for naming operations.
We suggest:
- Do not name entry point operations as
main
. - Name entry point operations as ordinary operations.
Shorthand and abbreviations
The previous advice notwithstanding, there are many forms of shorthand that see common and pervasive use in quantum computing.
We suggest using existing and common shorthand where it exists, especially for operations that are intrinsic to the operation of a target machine.
For example, we choose the name X
instead of ApplyX
, and Rz
instead of RotateAboutZ
.
When using such shorthand, operation names should be all uppercase (for example, MAJ
).
Some care is required when applying this convention in the case of commonly used acronyms and initialisms such as "QFT" for "quantum Fourier transform." We suggest following general .NET conventions for the use of acronyms and initialisms in full names, which prescribe that:
- two-letter acronyms and initialisms are named in upper case (for example,
BE
for "big-endian"), - all longer acronyms and initialisms are named in
CamelCase
(for example,Qft
for "quantum Fourier transform")
Thus, an operation implementing the QFT could either be called QFT
as shorthand, or written out as ApplyQft
.
For particularly commonly used operations and functions, it may be desirable to provide a shorthand name as an alias for a longer form:
operation CCNOT(control0 : Qubit, control1 : Qubit, target : Qubit)
is Adj + Ctl {
Controlled X([control0, control1], target);
}
We suggest:
- Consider commonly accepted and widely used shorthand names when appropriate.
- Use uppercase for shorthand.
- Use uppercase for short (two-letter) acronyms and initialisms.
- Use
CamelCase
for longer (three or more letter) acronyms and initialisms.
Proper nouns in names
While in physics it is common to name things after the first person to publish about them, most non-physicists aren’t familiar with everyone’s names and all of the history. Relying too heavily on naming conventions from physics can thus put up a substantial barrier to entry, as users from other backgrounds must learn a large number of seemingly opaque names in order to use common operations and concepts.
Thus, we recommend that wherever reasonable, common nouns that describe a concept be adopted in strong preference to proper nouns that describe the publication history of a concept.
As a particular example, the singly controlled SWAP and doubly controlled NOT operations are often called the "Fredkin" and "Toffoli" operations in academic literature, but are identified in Q# primarily as CSWAP
and CCNOT
.
In both cases, the API documentation comments provide synonymous names based on proper nouns, along with all appropriate citations.
This preference is especially important given that some usage of proper nouns will always be necessary — Q# follows the tradition set by many classical languages, for instance, and refers to Bool
types in reference to Boolean logic, which is in turn named in honor of George Boole.
A few quantum concepts similarly are named in a similar fashion, including the Pauli
type built-in to the Q# language.
By minimizing the usage of proper nouns where such usage is not essential, we reduce the impact where proper nouns cannot be reasonably avoided.
Type conversions
Since Q# is a strongly and statically typed language, a value of one type can only be used as a value of another type by using an explicit call to a type conversion function.
This is in contrast to languages which allow for values to change types implicitly (for example, type promotion), or through casting.
As a result, type conversion functions play an important role in Q# library development, and comprise one of the more commonly encountered decisions about naming.
We note, however, that since type conversions are always deterministic, they can be written as functions and thus fall under the earlier advice.
In particular, we suggest that type conversion functions should never be named as verbs (for example, ConvertToX
) or adverb prepositional phrases (ToX
), but should be named as adjective prepositional phrases that indicate the source and destination types (XAsY
).
When listing array types in type conversion function names, we recommend the shorthand Arr
.
Barring exceptional circumstances, we recommend that all type conversion functions be named using As
so that they can be quickly identified.
We suggest:
- If a function converts a value of type
X
to a value of typeY
, use either the nameAsY
orXAsY
.
Private or internal names
In many cases, a name is intended strictly for use internal to a library or project, and is not a guaranteed part of the API offered by a library.
It is helpful to clearly indicate that this is the case when naming functions and operations so that accidental dependencies on internal-only code are made obvious.
If an operation or function is not intended for direct use, but rather should be used by a matching callable which acts by partial application, consider using a name starting with the internal
keyword for the callable that is partially applied.
We suggest:
- When a function, operation, or user-defined type is not a part of the public API for a Q# library or program, ensure that it is marked as internal by placing the
internal
keyword before thefunction
,operation
, ornewtype
declaration.
Variants
Though this limitation may not persist in future versions of Q#, it is presently the case that there will often be groups of related operations or functions that are distinguished by which functors their inputs support, or by the concrete types of their arguments. These groups can be distinguished by using the same root name, followed by one or two letters that indicate its variant.
Suffix | Meaning |
---|---|
A |
Input expected to support Adjoint |
C |
Input expected to support Controlled |
CA |
Input expected to support Controlled and Adjoint |
I |
Input or inputs are of type Int |
D |
Input or inputs are of type Double |
L |
Input or inputs are of type BigInt |
We suggest:
- If a function or operation is not related to any similar functions or operations by the types and functor support of their inputs, do not use a suffix.
- If a function or operation is related to any similar functions or operations by the types and functor support of their inputs, use suffixes as in the previous table to distinguish variants.
Arguments and variables
A key goal of the Q# code for a function or operation is for it to be easily read and understood. Similarly, the names of inputs and type arguments should communicate how a function or argument will be used once provided.
We suggest:
- For all variable and input names, use
pascalCase
in strong preference toCamelCase
,snake_case
, orANGRY_CASE
. - Input names should be descriptive; avoid one or two letter names where possible.
- Operations and functions accepting exactly one type argument should denote that type argument by
T
when its role is obvious. - If a function or operation takes multiple type arguments, or if the role of a single type argument is not obvious, consider using a short capitalized word prefaced by
T
(for example,TOutput
) for each type. - Do not include type names in argument and variable names; this information can and should be provided by your development environment.
- Denote scalar types by their literal names (
flagQubit
), and array types by a plural (measResults
). For arrays of qubits in particular, consider denoting such types byRegister
where the name refers to a sequence of qubits that are closely related in some way. - Variables used as indices into arrays should begin with
idx
and should be singular (for example,things[idxThing]
). In particular, strongly avoid using single-letter variable names as indices; consider usingidx
at a minimum. - Variables used to hold lengths of arrays should begin with
n
and should be pluralized (for example,nThings
).
User-defined type named items
Named items in user-defined types should be named as CamelCase
, even in input to UDT constructors.
This helps in order to clearly separate named items from references to locally scoped variables when using accessor notation (for example, callable::Apply
) or copy-and-update notation (set arr w/= Data <- newData
).
We suggest:
- Named items in UDT constructors should be named as
CamelCase
; that is, they should begin with an initial uppercase. - Named items that resolve to operations should be named as verb phases.
- Named items that do not resolve to operations should be named as noun phrases.
- For UDTs that wrap operations, a single named item called
Apply
should be defined.
Input conventions
When a developer calls into an operation or function, the various inputs to that operation or function must be specified in a particular order, increasing the cognitive load that a developer faces in order to make use of a library. In particular, the task of remembering input orderings is often a distraction from the task at hand: programming an implementation of a quantum algorithm. Though rich IDE support can mitigate this to a large extent, good design and adherence to common conventions can also help to minimize the cognitive load imposed by an API.
Where possible, it can be helpful to reduce the number of inputs expected by an operation or function, so that the role of each input is more immediately obvious both to developers calling into that operation or function, and to developers reading that code later. Especially when it is not possible or reasonable to reduce the number of arguments to an operation or function, it is important to have a consistent ordering that minimizes the surprise that a user faces when predicting the order of inputs.
We recommend an input ordering conventions that largely derives from thinking of partial application as a generalization of currying 𝑓(𝑥, 𝑦) ≡ 𝑓(𝑥)(𝑦). Thus, partially applying the first arguments should result in a callable that is useful in its own right whenever that is reasonable. Following this principle, consider using the following order of arguments:
- Classical non-callable arguments such as angles, vectors of powers, etc.
- Callable arguments (functions and arguments). If both functions and operations are taken as arguments, consider placing operations after functions.
- Collections acted upon by callable arguments in a similar way to
Map
,Iter
,Enumerate
, andFold
. - Qubit arguments used as controls.
- Qubit arguments used as targets.
Consider an operation ApplyPhaseEstimationIteration
for use in phase estimation that takes an angle and an oracle, passes the angle to Rz
modified by an array of different scaling factors, and then controls applications of the oracle.
We would order the inputs to ApplyPhaseEstimationIteration
in the following fashion:
operation ApplyPhaseEstimationIteration(
angle : Double,
callable : (Qubit => () is Ctl),
scaleFactors : Double[],
controlQubit : Qubit,
targetQubits : Qubit[]
)
: Unit
...
As a special case of minimizing surprise, some functions and operations mimic the behavior of the built-in functors Adjoint
and Controlled
.
For instance, ControlledOnInt<'T>
has type (Int, ('T => Unit is Adj + Ctl)) => ((Qubit[], 'T) => Unit is Adj + Ctl)
, such that ControlledOnInt<Qubit[]>(5, _)
acts like the Controlled
functor, but on the condition that the control register represents the state $\ket{5} = \ket{101}$.
Thus, a developer expects that the inputs to ControlledOnInt
place the callable being transformed last, and that the resulting operation takes as its input (Qubit[], 'T)
--- the same order as followed by the output of the Controlled
functor.
We suggest:
- Use input orderings consistent with the use of partial application.
- Use input orderings consistent with built-in functors.
- Place all classical inputs before any quantum inputs.
Documentation conventions
The Q# language allows for attaching documentation to operations, functions, and user-defined types through the use of specially formatted documentation comments.
Denoted by triple-slashes (///
), these documentation comments are small DocFX-flavored Markdown documents that can be used to describing the purpose of each operation, function, and user-defined type, what inputs each expects, and so forth.
The compiler provided with the Quantum Development Kit extracts these comments and uses them to help typeset documentation similar to that used in Microsoft Learn documentation.
Similarly, the language server provided with the Quantum Development Kit uses these comments to provide help to users when they hover over symbols in their Q# code.
Making use of documentation comments can thus help users to make sense of code by providing a useful reference for details that are not easily expressed using the other conventions in this document. For more information, see the documentation comment syntax reference.
In order to effectively use this functionality to help users, we recommend keeping a few things in mind as you write documentation comments.
We suggest:
- Each public function, operation, and user-defined type should be immediately preceded by a documentation comment.
- At a minimum, each documentation comment should include the following sections:
- Summary
- Input
- Output (if applicable)
- Ensure that all summaries are two sentences or less. If more room is needed, provide a
# Description
section immediately following# Summary
with complete details. - Where reasonable, do not include math in summaries, as not all clients support TeX notation in summaries. Note that when writing prose documents (for example, TeX or Markdown), it may be preferable to use longer line lengths.
- Provide all relevant mathematical expressions in the
# Description
section. - When describing inputs, do not repeat the types of each input as these can be inferred by the compiler and risk introducing inconsistency.
- Provide examples as appropriate, each in their own
# Example
section. - Briefly describe each example before listing code.
- Cite all relevant academic publications (for example: papers, proceedings, blog posts, and alternative implementations) in a
# References
section as a bulleted list of links. - Ensure that, where possible, all citation links are to permanent and immutable identifiers (DOIs or versioned arXiv numbers).
- When an operation or function is related to other operations or functions by functor variants, list other variants as bullets in the
# See Also
section. - Leave a blank comment line between level-1 (
/// #
) sections, but do not leave a blank line between level-2 (/// ##
) sections.
Formatting conventions
In addition to the preceding suggestions, it is helpful to help make code as legible as possible to use consistent formatting rules. Such formatting rules by nature tend to be somewhat arbitrary and strongly up to personal aesthetics. Nonetheless, we recommend maintaining a consistent set of formatting conventions within a group of collaborators, and especially for large Q# projects such as the Quantum Development Kit itself. These rules can be automatically applied by using the formatting tool integrated with the Q# compiler.
We suggest:
Use four spaces instead of tabs for portability. For instance, in VS Code:
"editor.insertSpaces": true, "editor.tabSize": 4
Line wrap at 79 characters where reasonable.
Use spaces around binary operators.
Use spaces on either side of colons used for type annotations.
Use a single space after commas used in array and tuple literals (for example, in inputs to functions and operations).
Do not use spaces after function, operation, or UDT names in calls and declarations, or after the
@
in attribute declarations.Each attribute declaration should be on its own line.
Feedback
Submit and view feedback for