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
zip
andunzip
- Array literals?
- Generator expressions as static sequence initializers
- Optimized by Ki
- No need for
gather
in that case