Iteration
Ki iterates using the for-in loop, of which there are two incarnations:
fn main() {
var russian_authors: array(["Dostoyevsky", "Tolstoy", "Chekhov"])
var authors: array(["Dostoyevsky", "Kafka", "Vonnegut"])
for (&author in authors) {
if (author in russian_authors) {
echo("русский автор: ${author}")
}
else {
echo("Author: ${author}")
}
}
for (index, author in authors) {
echo("${index + 1}: ${author}")
}
}
Sometimes you just want numbers. Ki has the range literal for that:
fn main() {
for (i in [1..10]) {
echo("${i}")
}
}
If you iterate over a resizable container, you need a reference to it:
fn main() {
var names: dynarray(["Aaron", "Bathsheba", "Cain"])
# In order to iterate over a resizeable container, you need to take a
# reference to it. Because mutable references can't coincide with
# immutable references, this ensures that the container won't be resized,
# as that requires a mutable reference.
for (&name! in &names) {
name = "Zuul"
}
}
Iterator Generator Methods
Iterators all support a handful of methods that return a generator over the iterator.
The map method
map calls a function on each element of the iterable.
fn increment(int x): int {
return(x + 1)
}
fn main() {
val numbers: array([1, 2, 3, 4, 5, 6, 7, 8, 9])
val plus_one: numbers.map(increment)
}
The filter method
filter calls a function on each element of the iterable, and if that function
returns true, it yields the element. Otherwise it moves on to the next
element.
fn is_even(int x): bool {
return(x % 2 == 0)
}
fn increment(int x) (int) {
return(x + 1)
}
fn main() {
val numbers: array([1, 2, 3, 4, 5, 6, 7, 8, 9])
val evens: numbers.filter(is_even)
val evens_plus_one: evens.map(increment)
}
The reverse method
reverse simply iterates over the list backwards.
fn is_even(int x): bool {
return(x % 2 == 0)
}
fn increment(int x): bool {
return(x + 1)
}
fn main() {
val numbers: array([1, 2, 3, 4, 5, 6, 7, 8, 9])
val reversed: numbers.reverse()
val evens: reversed.filter(is_even)
val evens_plus_one: evens.map(increment)
}
reverse is technically a method of an indexable, not an iterable. That's why
it has to go first in the example.
The zip function
fn main() {
var names: dynarray(["Aaron", "Bathsheba", "Cain"])
var places: dynarray(["Egypt", "Jerusalem", "Nod"])
for (&name!, &place! in zip(&names!, &places!)) {
echo("${name} lived in ${place}")
}
}
Iterator Processors
Iterators also include four methods that process the iterator and return a scalar (a single value).
The fold method
Of all the iterator processors, fold is the most powerful. The canonical
example is summing a series of numbers.
fn main() {
val numbers: array([1, 2, 3, 4, 5, 6, 7, 8, 9])
val sum: numbers.fold(numbers, 0, +) # Returns 45
}
The any method
Calls a function on each element of the iterable and returns true the first
time the function returns true. If it reaches the end of the iterable, any
returns false. This short-circuits, so any elements in the iterable after
the one that returns true won't be processed.
The all method
Calls a function on each element of the iterable and returns false the first
time the function returns false. If it reaches the end of the iterable,
all returns true.
The none method
Calls a function on each element of the iterable and returns false the first
time the function returns true. If it reaches the end of the iterable,
none returns true.
To Do:
- How to define a new type as an iterator.
- start the iteration
- get the next value
- signal the end
- index modification expression
zipandunzip- Array literals?
- Generator expressions as static sequence initializers
- Optimized by Ki
- No need for
gatherin that case