Skip to content

Iterables

Here we cover builtin types that represent collections of elements that can be iterated. These are list, vector, map, range, and the complementary iter that helps loop through the elements of the rest. Structs overloading element access and length are also treated as iterables. Converting to iterators before looping is done automatically when using the in statement presented later.

Lists

Declare lists by separating values with a comma. Access and set their elements with square brackets where element indexes start from zero. You can also provide as indexes lists or other iterables that hold integer-only values. This retrieves multiple elements until the key's index goes out of bounds. Here is an example that also demonstrates usage of the len function to obtain the number of elements.

// main.bb
A = 1,2,3,4;
A[0] = 100;
print(A);
print(A|len);
print(A[range(1,3)]);
> ./blombly main.bb
(100, 2, 3, 4)
4
(2, 3)

You may initialize lists based on one element like below. This notation in general converts data to lists of one element. Otherwise, using a list a semit-type forces a conversion from other iterables.


An error is returned from functions that write on out-of-bounds elements, and a missing value error is returned when trying to access such elements. Use the as assignment to check for validity if list size is unknown, as errors are returned from functions that would add errors to lists instead of doing so.

// main.bb
A = list::element(5);
print(A);
A[3] = 0; // CREATES AN ERROR
> ./blombly main.bb
(5) 
( ERROR ) Out of range
     A[3]=0                                              main.bb line 4

Modifying len

The above examples do not modify lists during execution. To extract data while removing them from lists use the next and pop operators to obtain the first or last list elements respectively. These yield error values if the list is empty, so use the as assignment to detect whether the list was empty in loops. All list modifications are efficient; even front popping with next. Push elements to the end using the << operator.

// main.bb
A = 1,2,3;
A << 4;
while(a as A|next) print(a);
> ./blombly main.bb
1
2
3
4

Concatenation

Cconcatenate lists to leave the originals intact, like below.

// main.bb
print((1,2,3)+(4,5));
> ./blombly main.bb
(1,2,3,4,5)

Tip

Pushing to lists returns the list itself, for example to enable syntax like A = list()<<1<<2<<3;.

Iterators

Given any data structure that accepts an element getter and length methods, you can create an iterator to traverses through its elements without modifying it. Such data include lists and are called iterables. Obtain an iterator from an iterable with the iter typecasting as in the example below. Iterators admit only the next operator, and always accept a conversion to iter that returns themselves. One cannot modify data, perform random access, or restart traversal of the iterable's elements. Create a new one to go through data that are already consumed.

// main.bb
A = 1,2,3;
it = iter(A);
while(i as it|next) print(i);
> ./blombly main.bb
1
2
3

Info

Iterators are safe in that they do not crash while the data structure they are iterating through is modified, for example by adding, changing, or removing elements. The iterator may miss parts of the modifications it has already traversed through.

in

The blombly compiler preloads a couple of common macros in libs/.bb. The important part for iterables is that an in operator is provided. This preconstructs an iterator from any given data type and runs as next on it. Here is an example.

A = 1,2,3;
print("Original length !{A|len}");
while(i in A) print(i);
print("Length after iteration !{A|len}");
> ./blombly main.bb
Original length 3
1
2
3
Length after iteration 3


!gather

A second macro designed for comprehensive iterable manipulation is !gather(@init, @aggregate) {@body}. The exclamation mark is treated as a normal character and is purely cosmetic, but it lets you know that the macro affects control flow. This macro contains an initialization statement, a binary self-modification operator that is applied on every new element, and a body whose return statements are modified to signify what is gathered. Here is an example of how to filter through an iterable's elements:

A = 1,2,3,4,5,6;
B = !gather(list(),<<) {while(x in A) if(x%2==0) return x;}
print(B);
> ./blombly main.bb
(2,4,6)

Ranges

Blombly provides ranges that go through a specified set of numbers. Like other iterators, ranges are only consumed once and need to be created anew to be repeated. There are four construction patterns shown below. You can also construct an iterator that produces random numbers ad infinitum per random(seed) where the seed is either an int or a float. Those numbers will be in the range [0,1].

Function Signature Description
range(int end) Creates a range from 0 up to end-1 with a step of 1.
range(int start, int end) Starts from the specified start number and goes up to end-1.
range(int start, int end, int step) Similar to the previous version but allows specifying the step size.
range(float start, float end, float step) Supports real numbers or a mix of real and integer numbers (error otherwise).
random(seed) Generates an iterator that yields uniformly random numbers in the range [0,1].

Negative steps are also allowed. Below is a demonstration.

// main.bb
it = range(2, 0, -0.5);
while(i as it|next) print(i);
> ./blombly main.bb
2.000000 
1.500000
1.000000
0.500000

Vectors

Vectors correspond collections to consecutive float values. They differ from lists in that they hold only floats and have a fixed size. This known information is leveraged to provide performant implementations of scientific computations.


Perform element-by-element arithmetic operations on them for fast scientific computations. Treat them as lists of values whose elements can not be inserted or modified - only set. Element default values are zeros. Vectors can be created either directly, or be converted from a list of numbers. They also support sub-indexing from iterables yielding integer identifiers. This is especially fast when ranges are provided, as in the following example.

// main.bb
x = vector(1,2,3,4,5);
y = vector(1,2,3,4,5);
x[1] = 1; // modify element
z = x+y-1;
z = z[range(3)];
print(z);
> ./blombly main.bb
(1.000000, 2.000000, 5.000000)

Direct allocation

Convert any iterable to a vector with the above syntax, provided that it contains only floats. Initialize vectors of zero elements or -for even faster allocation- no set values. This is done like below. Recall that :: is treated as a normal name character by the language.

zeros = vector::zero(1000);
print(zeros|len, zeros|sum);

unset = vector::alloc(1000); // this has memory garbage
print(unset|len, unset|sum);
> ./blombly main.bb
(1000, 0.000000)
(1000, 235856725974...)

Maps

Maps associate struct or non-error primitive keys to values. Set their elements or provide an iterable of (key, value) pairs, that is, a list of two elements. For keys, numbers and strings are considered the same based on their value, whereas other data match only specific objects. Maps implement member access and set operators. But they are also iterables that yield key, value pairs as lists of two entries. Pop from the front or back of the pair with the | notation to extract keys and values with concise syntax that retains clear semantics. Finally, concatenate maps by adding them.

// main.bb
A = map(
    ("A", 1), 
    ("B", 2), 
    ("C", 3));
A["D"] = 4;

print("---- Pairs");
while(pair in A) print(pair);
print("---- Keys");
while(pair in A) print(pair|next);
> ./blombly main.bb
---- Pairs
[B, 2] 
[C, 3] 
[A, 1] 
[D, 4] 
---- Keys
B
C
A
D

Tip

Blombly does not implement sets. Instead, treat those as maps from objects to true.