Beryllium is a purpose-built quantum programming language designed to bridge the divide between classical and quantum development. It brings classical constructs like function pointers and object-oriented concepts like class inheritance and method overrides to quantum programming. Classes can be used to encapsulate a combination of classical and quantum data types, so developers can work with classical data types while also incorporating quantum data types within a single program.
Beryllium also introduces additional control flow structures, giving developers more flexibility in how their quantum programs flow, and empowering them to write shorter, more efficient code.
By abstracting away the details of quantum execution, Beryllium shifts the focus toward algorithm design rather than low-level implementation. This abstraction begins to enable collaboration between quantum and classical developers, allowing classical developers to harness classes and modules written by those with quantum expertise.
Let’s take a closer look at some of Beryllium’s advanced capabilities.
Elif and else statements
In addition to if statements, Beryllium allows programmers to write conditional statements, including elif and else statements, so they can build complex quantum programs without relying on numerous if statements.
The code block below shows a program with if, elif, and else statements.
In the example above:
- Line 2 creates a variable n and sets it to 1.
- Line 3 creates a variable answer, which starts at 0, and the program will update as it runs.
- Line 4 introduces a condition (a check to see if something is true), asking whether n is 0. If it is, line 5 sets answer to 1.
- Line 7 introduces elif (short for “else if”), which runs only if the first condition is not met. It checks whether n is 1, and if so, line 8 sets answer to 1.
- Line 10 introduces an else statement, which runs if neither condition has been met, with line 11 instructing the program to set answer to 100.
In this example, n is 1, so the elif condition is met, and answer ends up as 1.
Switch statements
Beryllium allows developers to express complex logic using switch statements. Rather than writing numerous chains of if, elif, and else blocks, a developer can create switch statements that let a program choose between several options based on the value of a numeric variable. Each option (or “case”) corresponds to a specific value, and the program runs the matching block of code. If none of the cases match, the program runs a default action instead.
Here’s a simple example of how this control flow statement can be used to perform operations on qubit q1, depending on the value of the switch variable my_operation.
In the code block above:
- Line 2 creates a single qubit q1 (a basic two-state quantum system), defined as qusys(2).
- Line 3 creates a numeric constant pi set to 3.14159.
- Line 6 creates a variable my_operation set to 3.
The program then runs switch based on the value of my_operation (line 8). Each possible value corresponds to a different operation (quantum gate) applied to q1.
After the switch, the program measures q1 and stores the measurement outcome (as a classical variable) in result (line 31). It then prints result as output (line 32).
Because my_operation is 3, only one matching case runs: z[q1]. Switch statements in Beryllium do not have “fall-through” behavior, meaning that once a matching case is found, the program runs that case only and does not continue into the others.
z[q1] applies a Z gate to q1. A Z gate only changes the qubit if it is in the |1⟩ state. Since q1 starts in the default |0⟩ state, the gate has no effect in this case, and the state remains unchanged.
As a result, when the qubit is measured, it returns 0, and this value is displayed as the program’s output.
For loops
Beryllium allows users to write for loops (a way to repeat a set of steps automatically). Similar to C/C++, Beryllium's for loops contain three components: initialization, condition, and increment.
The code block below instructs a program to sum the first 10 integers.
In the example above, line 2 creates a variable sum and sets it at 0.
Line 4 introduces a for loop. It sets up where the loop starts (initialization), what it checks (condition), and how it updates each time it runs (increment). It starts by creating a variable index, which keeps track of the current number, and sets it to 1. It then checks a condition: the loop continues as long as index is less than or equal to 10.
Each time the loop runs, index increases by 1, moving it closer to the point where the condition is no longer true. Line 5 runs each time the loop repeats, adding the current value of index to sum
Once index reaches 11, the condition is no longer true, so the loop exits (stops). At this point, sum has the value 55, which is the total sum of the integers from 1 to 10.
Beryllium also supports decrementing loops, and it allows the assignment of increment values with other arithmetic operations such as multiplication and division.
While loops
Beryllium’s while loops enable developers to write indefinite loops, which keep running until a condition is no longer true.
In the example program above:
- Lines 2 to 4 create three variables: limit = 9, swap1 = 1, and swap2 = 2.
- Line 6 instructs the program to enter a while loop that runs as long as limit is greater than 7, meaning it will stop once limit reaches 7. Each time the loop runs, it updates the values of swap1 and swap2 in a series of steps, using the latest values each time, as described in lines 7, 8, and 9.
- Line 10 then decreases limit by 1, bringing it closer to the point where the loop will stop. Because limit starts at 9 and decreases by 1 each time, the loop runs twice—when limit is 9 and 8—before stopping.
When the code above finishes, swap1 is 7, swap2 is -4, and limit is 7. This program does not include → output, so it produces no displayed results.
Jump statements
Using break and continue, Triple Alpha users can create jump statements (instructions that change how a loop progresses) that alter the flow of execution for and while loops based on logic. When a certain condition is met, break allows users to exit the loop entirely; continue allows them to skip the rest of a loop cycle and move to the next iteration.
In the example below, break is conditioned on an if statement.
In the code block above lines 2 and 3 create two variables: max, set at 10, and curr, set at 0.
Line 5 executes a while loop that runs as long as curr is less than max. Each time the loop runs, it evaluates a three-way branch (a set of three possible conditions that determine what happens next) based on the current value of curr.
If curr is 3, the program adds 2 to curr, making it 5, and then executes continue (lines 6-8). This skips the rest of that loop cycle and immediately returns to check the loop condition again.
If curr is 7 instead (elif), the program executes break, which exits the loop immediately (lines 10 to 11). Otherwise (else), the program increases curr by 1 and progresses as normal (lines 13 to 14).
Because of this logic, the loop will run seven times, behaving in a specific way. On the fourth time the loop runs, curr reaches 3, so the first condition applies. Instead of increasing by 1, curr jumps from 3 to 5, and the loop immediately moves to the next cycle.
On the seventh time the loop runs, curr reaches 7, so the second condition applies, and the loop terminates early, before curr can reach max.
When the program finishes, curr is 7 and max is 10. This program does not contain the instruction → output, so the program does not display any results.
Functions
Functions let developers write a block of code once and reuse it wherever it’s needed. They help keep codebases modular and readable, making them easier to debug and maintain.
Unlike subroutines in Helium (Triple Alpha's BASIC-like programming language), Beryllium's functions are scoped, so the body of a function can only interact with the rest of the program through its input and output arguments, which allows for much stronger isolation and modularity.
Every Beryllium program begins with a main() function as its entry point for execution. Developers can define additional functions outside of main(), using the func keyword.
Each function has a name, an ordered list of typed inputs, and an optional named return variable with its own type. Together, these components form the function’s signature.
A function in Beryllium can return, at most, one variable. To specify the return value, a developer assigns it to the named return variable. If a routine needs to produce multiple results, those results can be grouped into a struct and returned as a single value.
Beryllium functions can also call themselves, enabling recursion, and developers can use the return keyword to exit a function early.
The code block below defines a function that applies gates to two qubits, measures them, and combines the measurement outcomes with a classical input.
In the example above:
- Line 1 declares a function called calculate, taking three inputs—a number a and two qubits qa and qb—to return a numeric value c. (Everything before the opening brace is the function’s signature.)
- Lines 2 to 4 apply an X gate to qa, a Hadamard gate to qb, and a CNOT gate across both qubits.
- Lines 5 and 6 measure both qubits, storing each outcome in a classical numeric variable.
- Line 7 assigns the result to c, the function’s named return variable.
- Line 14 calls the function from main(), passing the numerical value 2 alongside the two qubits, and captures the outcome in result.
Function pointers
Beryllium also supports function pointers, which allow developers to store references to functions rather than calling them directly. Function pointers let a program decide which routine to execute while it runs, allowing developers to call hybrid quantum-classical subroutines dynamically without having to hardcode complex branching logic.
Developers can pass function pointers between functions or store them in data structures. A function pointer can be used to invoke function calls like an ordinary function, passed into another function as an argument, stored in an array, or held as a field within a struct.
A function pointer's type is defined by a reference to an existing function with the desired signature. This reference function establishes the pointer's type, and any function with the same signature can then be assigned to it. The * symbol indicates that a declaration is a function pointer.
The code block below declares a function pointer, assigns a function to it, is used to invoke a function through it, and then repoints it to a different function with the same signature.
In the code block above:
- Line 4 declares a function pointer clause_checker_ptr. The type is borrowed from check_via_measure(), an existing function. Any function sharing that signature can be assigned.
- Line 7 points it as check_via_measure(), and line 8 invokes that function through the pointer.
- Line 12 reassigns the same pointer to check_via_measure_2(), a different function with the same signature, and line 13 invokes it.
A function pointer can also be declared as a parameter, using the same syntax, which lets one function take another as an input.
Structs
Beryllium provides structs: user-defined data types that allow related pieces of data to be grouped together under a single variable.
Structs are defined at the top level, outside any function, using the struct keyword. They can contain both classical and quantum types. They are a lightweight way to keep everything a quantum routine needs in one place that can be passed around as a single variable. For instance, a struct might include the qubit a routine acts on, the classical values that configure it, and the arrays it reads from. By bundling a quantum system alongside relevant classical data, structs help developers keep logically connected data organized.
The code below defines a struct for a Grover search routine and creates an instance of it.
In the above code block, lines 1 to 7 define a struct called Grover, grouping together everything that a Grover search needs: two arrays of qubits (lines 2 to 3), two numeric values (lines 4 to 5), and a numeric array of input parameters (line 6). Both classical and quantum types sit together in the same structure.
Line 11 creates an instance of the struct, called g1. Declaring a struct variable works the same way as declaring a class variable in Beryllium.
Lines 17 and 18 then write values into that instance’s properties, by using the dot operator.
Structs can also be collected into arrays declared as Grover[], and they can be nested inside one another.
Classes
Beryllium lets developers structure programs around objects, which bundle together data (properties) and behavior (methods). Classes are used to define objects: they are templates that specify the properties and methods shared by a group of objects. Each object is an instance of a class, representing a concrete entity, and classes enable code reuse and consistent behavior across many such instances.
In the above code block, the user defines a Sword class. Objects in this class have one property, a numeric damage value (lines 1 to 4). Lines 5 to 12 define how each Sword object works: the damage value is stored in the object, and the method swing() returns that value.
In the program main, line 17 creates a sword excalibur with damage 100, and line 18 creates a sword caliburn with damage 60.
The program then creates an array called swords, which is used to store multiple Sword objects together (line 21). Because the array is defined for the Sword class, it can only contain Sword objects.
The two swords are placed into the array, with excalibur at position 1 (line 24) and caliburn at position 2 (line 25).
In lines 28 through 30, the program reads the first sword back out of the array and stores it in a variable called first_sword (which refers to the same object as excalibur). Next, the program calls first_sword.swing(), which returns the sword’s damage value (100), and stores it in a variable called damage. Finally, damage → output writes this value (100) as the program’s result.
In this program, the array swords holds both sword objects, but only the first one, excalibur, is used to produce the output. The second sword, caliburn, is stored but not referenced as part of the output.
Arrays
Arrays are a data structure that allows programs to store multiple variables of the same type together. Working in Beryllium, developers can create arrays that contain either quantum or classical variables. They can also create arrays of objects that have been described by classes (objects of a single class).
When building an array of objects, developers can store objects from a specific class in a single structure. By storing these objects in an array, developers can organize and access related objects more easily, work through them in loops, and pass groups of related objects into functions in one step, without needing to create each one as an individual variable.
Module imports
Similar to how classical programmers use Python packages, Triple Alpha users can organize code in multiple Beryllium files as modules. They can import functions and classes from one Beryllium file containing a module into another so that developers only have to write the function one time.
A shift in quantum software development
With these capabilities, we believe Beryllium marks a shift in how quantum software is developed. By introducing familiar control flow constructs, modular architectures, and reusable components, it enables developers to approach quantum programming using patterns aligned with modern classical software engineering. This higher level of abstraction improves accessibility, making quantum applications easier to understand, extend, and potentially scale over time.
Table of Contents


