Arrays¶
In Guppy, an array is an ordered collection of objects of the same type, with a size that is fixed and known at compile time. These two properties distinguish arrays from Python lists.
Arrays are mutable: their values can be reassigned at runtime.
An array can be created using the array
constructor. The type signature is array[T, n]
where T
is the type of the data and n
is the size of the array.
from guppylang import guppy
from guppylang.std.builtins import array
@guppy
def get_array() -> array[int, 3]:
return array(0, 2, 4)
Note that in Guppy it is necessary to annotate both the type and the size of an array in function signatures.
Array entries can be changed as follows:
@guppy
def mutate_array() -> array[int, 3]:
numbers = get_array() # Create array containing 0, 2 and 4
numbers[0] = 17 # Change first element to 17
return numbers # Return modified array
mutate_array.check()
Arrays can also be nested, meaning that the elements of an array can themselves be arrays.
@guppy
def get_array_of_arrays() -> array[array[int, 4], 3]:
return array(array(1, 2, 3, 4), array(2, 4, 6, 8), array(3, 6, 9, 12))
get_array_of_arrays.check()
Indexing into arrays¶
As in Python, Guppy indices start from zero. In the array arr = array(0, 2, 4)
we can access the element 0
with arr[0]
, 4
with arr[2]
, and so on.
Warning
Although the size of an array is known at compile time, the index may not be. If an index computed at runtime is out of bounds, a runtime error will occur.
Array comprehensions¶
We can use array comprehension to create an array object without specifying all of its elements individually. This is especially useful for dealing with large arrays.
Syntactically, Guppy comprehensions are similar to list comprehensions in Python.
@guppy
def get_first_four_squares() -> array[int, 4]:
return array(x*x for x in range(4))
get_first_four_squares.check()
Note that as the size of an array has to be statically known we cannot generalize this function using a generic variable.
n = guppy.nat_var("n")
@guppy
def get_first_n_squares(n: int) -> array[int, n]:
return array(x*x for x in range(n))
get_first_n_squares.check()
Error: Array comprehension with nonstatic size (at <In[5]>:5:16)
|
3 | @guppy
4 | def get_first_n_squares(n: int) -> array[int, n]:
5 | return array(x*x for x in range(n))
| ^^^^^^^^^^^^^^^^^^^^^^^ Cannot infer the size of this array comprehension ...
|
5 | return array(x*x for x in range(n))
| -------- since the number of elements yielded by this iterator is not
| statically known
Guppy compilation failed due to 1 previous error
For more background on Guppy’s static type checker see the section on Static Compilation and Typing.
We cannot use conditional statements in array comprehensions as their values generally can’t be known at compile time.
@guppy
def filter_squares_by_divisor(divisor: int) -> array[int, 3]:
squares = get_first_four_squares()
return array(x for x in squares if x % divisor == 0) # Size cannot be determined statically
filter_squares_by_divisor.check()
Error: Array comprehension with nonstatic size (at <In[6]>:4:16)
|
2 | def filter_squares_by_divisor(divisor: int) -> array[int, 3]:
3 | squares = get_first_four_squares()
4 | return array(x for x in squares if x % divisor == 0) # Size cannot be determined statically
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Cannot infer the size of this array comprehension ...
|
4 | return array(x for x in squares if x % divisor == 0) # Size cannot be determined statically
| ---------------- since it depends on this condition
Guppy compilation failed due to 1 previous error
Array unpacking¶
The elements within a Guppy array can be accessed via unpacking similarly to Python tuples. To see how unpacking works for Guppy tuples see the tuple unpacking section.
We can use the *
operator to unpack multiple elements.
@guppy
def make_array() -> array[int, 4]:
return array(5, 10, 15, 20)
@guppy
def unpack_tail(arr: array[int, 4]) -> tuple[int, array[int, 3]]:
first, *tail = make_array()
return first, tail
unpack_tail.check()
A current limitation of array unpacking is that it is not supported for arrays of generic length.
Note
Note that it in Guppy it is possible to unpack any iterable type.
For example we can unpack a Range
as follows first, *tail = range(10)
.
Moving and copying arrays¶
Guppy arrays are affine, meaning their value can be used once or not at all. Assignment of arrays does not copy their values into a new array, but just moves the reference.
@guppy
def make_big_array() -> array[int, 96]:
return array(x*x for x in range(96))
@guppy
def main() -> None:
arr1 = make_big_array()
arr2 = arr1 # Move the value arr1 to arr2
arr1[1] = 17 # Compiler error, arr1 cannot be indexed into after the move
main.check()
Error: Copy violation (at <In[8]>:9:4)
|
7 | arr1 = make_big_array()
8 | arr2 = arr1 # Move the value arr1 to arr2
9 | arr1[1] = 17 # Compiler error, arr1 cannot be indexed into after the move
| ^^^^^^^ Variable `arr1` with non-copyable type `array[int, 96]`
| cannot be borrowed ...
|
8 | arr2 = arr1 # Move the value arr1 to arr2
| ---- since it was already moved here
Help: Consider copying `arr1` instead of moving it: `arr1.copy()`
Guppy compilation failed due to 1 previous error
Assignment of an array to the new arr2
variable moves the value of arr1
to arr2
. The value of arr1
cannot be used after it is moved.
Arrays can still be copied explicitly using the array.copy()
method if they contain objects with a copyable type.
@guppy
def main() -> None:
arr1 = make_big_array()
arr2 = arr1.copy() # Explicitly copy arr1 and assign to arr2
arr1[95] = 419 # arr1 can still be used as it hasn't been moved
main.check()
Explicit copying is a design choice with performance implications. Arrays can be large, and copying can be a significant memory overhead.
Array copying therefore has to be explicitly opted into via the array.copy()
method rather than done implicitly with variable assignment.
Note that arrays cannot be copied after a move.
@guppy
def main() -> None:
arr1 = make_big_array()
arr2 = arr1 # Move the value arr1 to arr2
arr3 = arr1.copy() # Compiler error
main.check()
Error: Copy violation (at <In[10]>:5:11)
|
3 | arr1 = make_big_array()
4 | arr2 = arr1 # Move the value arr1 to arr2
5 | arr3 = arr1.copy() # Compiler error
| ^^^^ Variable `arr1` with non-copyable type `array[int, 96]`
| cannot be borrowed ...
|
4 | arr2 = arr1 # Move the value arr1 to arr2
| ---- since it was already moved here
Help: Consider copying `arr1` instead of moving it: `arr1.copy()`
Guppy compilation failed due to 1 previous error
Arrays of non-copyable types, such as qubits, cannot be copied. Also if an array contains qubits, it cannot be implicitly discarded.
It must be discarded explicitly with the discard_array
function to avoid violating linearity.
Nested arrays cannot be copied directly. A two-dimensional array can be copied via comprehension as follows.
@guppy
def make_2d_array() -> array[array[int, 3], 3]:
return array(array(1, 2, 3), array(1, 4, 9), array(1, 8, 27))
@guppy
def main() -> None:
arr = make_2d_array()
# arr.copy() # would give a compiler error
copied_arr = array(inner.copy() for inner in arr)
copied_arr[1][1] = 31
main.check()
Note that for
loops currently take ownership of the iterable, which is useful to keep in mind when you are iterating directly over arrays as opposed to using subscripts:
from guppylang.std.builtins import owned
m = guppy.nat_var("m")
@guppy
def f(x: int) -> None:
pass
@guppy
def apply_f(xs: array[int, m] @ owned) -> array[int, m]:
for x in xs:
f(x)
return xs
apply_f.check()
Error: Copy violation (at <In[12]>:13:11)
|
11 | for x in xs:
12 | f(x)
13 | return xs
| ^^ Variable `xs` with non-copyable type `array[int, m]` cannot
| be returned ...
|
11 | for x in xs:
| -- since it was already consumed here
Help: Consider copying `xs` instead of moving it: `xs.copy()`
Guppy compilation failed due to 1 previous error
Explicit copying can come in handy here, if it is possible to do with the array that is being iterated over:
@guppy
def apply_f(xs: array[int, m] @ owned) -> array[int, m]:
for x in xs.copy():
f(x)
return xs
apply_f.check()
Example usage of arrays¶
To see some uses of arrays in practice, refer to the following examples: