mirror of
https://github.com/janet-lang/janet
synced 2024-12-27 00:40:26 +00:00
175 lines
5.5 KiB
Markdown
175 lines
5.5 KiB
Markdown
# Loops in Janet
|
|
|
|
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 is more convenient to use a explicit loop to
|
|
iterate over a collection like an array.
|
|
|
|
## An Example - 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
|
|
even with taking shortcuts. 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 macro to iterate, similar
|
|
to how one might sum natural numbers in a language such as C.
|
|
|
|
```
|
|
(var sum 0)
|
|
(var i 0)
|
|
(while (< i 10)
|
|
(+= sum i)
|
|
(++ i))
|
|
(print sum) # prints 45
|
|
```
|
|
This is a very imperative style program which can grow very large very quickly.
|
|
We are manually updating a counter `i` in a loop. Using the macros `+=` and `++`, this
|
|
style code is similar in density to C code.
|
|
It is recommended to use either macros (such as the loop macro) 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)` captures exactly this behavior of incrementing a counter
|
|
in a loop.
|
|
|
|
```
|
|
(var sum 0)
|
|
(for i 0 10 (+= sum i))
|
|
(print sum) # 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 sum 0)
|
|
(loop [i :range [0 10]] (+= sum i))
|
|
(print sum) # 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 sum 0)
|
|
(loop [i :range [0 10] :when (even? i)] (+= sum i))
|
|
(print sum) # prints 20
|
|
```
|
|
|
|
The loop macro has several verbs (:range) and modifiers (:when) that let
|
|
the programmer more easily generate common looping idioms. The loop macro
|
|
is similar to the Common Lips 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.
|
|
|
|
## Another Example - Iterating an Indexed Data Structure
|
|
|
|
Another common usage for iteration in any language is iterating over the items in
|
|
some 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 the 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))
|
|
```
|
|
|
|
## 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, lets 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.
|
|
|
|
```
|
|
(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 the dot syntax for indexing
|
|
|
|
```
|
|
(loop [letter :keys alphabook]
|
|
(print letter " is for " alphabook.letter))
|
|
```
|
|
|
|
The symbol `alphabook.letter` is shorthand for `(get alphabook letter)`.
|
|
Note that the dot syntax of `alphabook.letter` is different than in many languages. In C or
|
|
ALGOL like languages, it is more akin to the indexing operator, and would be written `alphabook[letter]`.
|
|
The `.` character is part of the symbol and is recognized by the compiler.
|
|
|
|
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))
|
|
```
|