Janet 1.38.0-73334f3 Documentation
(Other Versions:
1.37.1
1.36.0
1.35.0
1.34.0
1.31.0
1.29.1
1.28.0
1.27.0
1.26.0
1.25.1
1.24.0
1.23.0
1.22.0
1.21.0
1.20.0
1.19.0
1.18.1
1.17.1
1.16.1
1.15.0
1.13.1
1.12.2
1.11.1
1.10.1
1.9.1
1.8.1
1.7.0
1.6.0
1.5.1
1.5.0
1.4.0
1.3.1
)
Looping
A very common and essential operation in all programming is looping. Most
languages support looping of some kind, either with explicit loops or recursion.
Janet supports both recursion and a primitive while
loop. While recursion
is useful in many cases, sometimes it is more convenient to use an explicit loop
to iterate over a collection like an array.
Example 1: Iterating a range
Suppose you want to calculate the sum of the first 10 natural numbers 0 through 9. There are many ways to carry out this explicit calculation. A succinct way in Janet is:
(+ ;(range 10))
We will limit ourselves however to using explicit looping and no functions like
(range n)
which generate a list of natural numbers for us.
For our first version, we will use only the while
form to iterate,
similar to how one might sum natural numbers in a language such as C.
(var total 0)
(var i 0)
(while (< i 10)
(+= total i)
(++ i))
(print total) # prints 45
This is a very imperative program, and it is not the best way to write this in
Janet. We are manually updating a counter i
in a loop. Using the macros
+=
and ++
, this style of code is similar in density to C code. It is
recommended to instead use either macros (such as the loop
or for
macros) or a functional style in Janet.
Since this is such a common pattern, Janet has a macro for this exact purpose.
The (for x start end body)
form captures this behavior of incrementing a
counter in a loop.
(var total 0)
(for i 0 10 (+= total i))
(print total) # prints 45
We have completely wrapped the imperative counter in a macro. The for
macro, while not very flexible, is very terse and covers a common case of
iteration: iterating over an integer range. The for
macro will be
expanded to something very similar to our original version with a while
loop.
We can do something similar with the more flexible loop
macro.
(var total 0)
(loop [i :range [0 10]] (+= total i))
(print total) # prints 45
This is slightly more verbose than the for
macro, but can be more easily
extended. Let's say that we wanted to only count even numbers towards the sum.
We can do this easily with the loop
macro.
(var total 0)
(loop [i :range [0 10] :when (even? i)] (+= total i))
(print total) # prints 20
The loop
macro has several verbs (e.g. :range
) and
modifiers (e.g. :when
) that let the programmer more easily
generate common looping idioms. The loop
macro is similar to
the Common Lisp loop
macro, but smaller in scope and with a
much simpler syntax. As with the for
macro, the loop
macro expands to similar code as our original while
expression.
Example 2: Iterating over an indexed data structure
Another common usage for iteration in any language is iterating over the items in a data structure, like items in an array, characters in a string, or key-value pairs in a table.
Say we have an array of names that we want to print out. We will again start
with a simple while
loop which we will refine into more idiomatic
expressions.
First, we will define our array of names:
(def names
@["Jean-Paul Sartre"
"Bob Dylan"
"Augusta Ada King"
"Frida Kahlo"
"Harriet Tubman"])
With our array of names, we can use a while
loop to iterate through the
indices of names, get the values, and then print them.
(var i 0)
(def len (length names))
(while (< i len)
(print (get names i))
(++ i))
This is rather verbose. Janet provides the each
macro for iterating
through the items in a tuple or array, or the bytes in a buffer, symbol, or
string.
(each name names (print name))
We can also use the loop
macro for this case as well using the :in
verb.
(loop [name :in names] (print name))
Lastly, we can use the map
function to apply a function over each value
in the array.
(map print names)
The each
macro is actually more flexible than the normal loop, as it is
able to iterate over data structures that are not like arrays. For example,
each
will iterate over the values in a table.
Example 3: Iterating a dictionary
In the previous example, we iterated over the values in an array. Another common
use of looping in a Janet program is iterating over the keys or values in a
table. We cannot use the same method as iterating over an array because a table
or struct does not contain a known integer range of keys. Instead we rely on a
function next
, which allows us to visit each of the keys in a struct or
table. Note that iterating over a table will not visit the prototype table.
As an example, let's iterate over a table of letters to a word that starts with that letter. We will print out the words to our simple children's book.
(def alphabook
@{"A" "Apple"
"B" "Banana"
"C" "Cat"
"D" "Dog"
"E" "Elephant"})
As before, we can evaluate this loop using only a while
loop and the
next
function. The next
function is the primary way to iterate in
Janet, and is overloaded to support all iterable types. Given a data structure
and a key, it returns the next key in the data structure. If there are no more
keys left, it returns nil
.
(var key (next alphabook nil))
(while (not= nil key)
(print key " is for " (get alphabook key))
(set key (next alphabook key)))
However, we can do better than this with the loop
macro using the
:pairs
or :keys
verbs.
(loop [[letter word] :pairs alphabook]
(print letter " is for " word))
Using the :keys
verb and shorthand notation for indexing a data
structure:
(loop [letter :keys alphabook]
(print letter " is for " (alphabook letter)))
As an alternative to the loop
macro here, we can also use the macros
eachk
and eachp
, which behave like each
but loop
over the keys of a data structure and the key-value pairs in a data structure
respectively.
Data structures like tables and structs can be called like functions that look
up their arguments. This allows for writing shorter code than what is possible
with (get alphabook letter)
.
We can also use the core library functions keys
and pairs
to get
arrays of the keys and pairs respectively of the alphabook.
(loop [[letter word] :in (pairs alphabook)]
(print letter " is for " word))
(loop [letter :in (keys alphabook)]
(print letter " is for " (alphabook letter)))
Notice that iteration through the table is in no particular order. Iterating the
keys of a table or struct guarantees no order. If you want to iterate in a
specific order, use a different data structure or the (sort indexed)
function.
Note that the above examples can also be expressed as:
(loop [[letter word] :pairs alphabook]
(print letter " is for " word))
(loop [letter :keys alphabook]
(print letter " is for " (alphabook letter)))