mirror of
				https://github.com/janet-lang/janet
				synced 2025-11-03 17:13:10 +00:00 
			
		
		
		
	Add some documentation for looping and the loop macro.
Also add :pairs verb to the loop macro and some more tests.
This commit is contained in:
		@@ -162,6 +162,6 @@ See the examples directory for some example janet code.
 | 
			
		||||
 | 
			
		||||
## Why Janet
 | 
			
		||||
 | 
			
		||||
Janet is named after the omniscient and friendly artificial being in [The Good Place](https://en.wikipedia.org/wiki/The_Good_Place).
 | 
			
		||||
Janet is named after the almost omniscient and friendly artificial being in [The Good Place](https://en.wikipedia.org/wiki/The_Good_Place).
 | 
			
		||||
 | 
			
		||||
<img src="https://raw.githubusercontent.com/janet-lang/janet/master/assets/janet-the-good-place.gif" alt="Janet logo" width="115px" align="left">
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										148
									
								
								doc/Loop.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								doc/Loop.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,148 @@
 | 
			
		||||
# 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 primitve `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
 | 
			
		||||
 | 
			
		||||
Supose 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 funcitions
 | 
			
		||||
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))
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
For ou
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -165,7 +165,7 @@
 | 
			
		||||
 | 
			
		||||
(defmacro if-not
 | 
			
		||||
  "Shorthand for (if (not ... "
 | 
			
		||||
  [condition exp-1 exp-2]
 | 
			
		||||
  [condition exp-1 exp-2 &]
 | 
			
		||||
  ~(if ,condition ,exp-2 ,exp-1))
 | 
			
		||||
 | 
			
		||||
(defmacro when
 | 
			
		||||
@@ -290,6 +290,7 @@
 | 
			
		||||
  \t:range - loop over a range. The object should be two element tuple with a start
 | 
			
		||||
  and end value. The range is half open, [start, end).\n
 | 
			
		||||
  \t:keys - Iterate over the keys in a data structure.\n
 | 
			
		||||
  \t:pairs - Iterate over the keys value pairs in a data structure.\n
 | 
			
		||||
  \t:in - Iterate over the values in an indexed data structure or byte sequence.\n
 | 
			
		||||
  \t:generate - Iterate over values yielded from a fiber. Can be paired with the generator
 | 
			
		||||
  function for the producer/consumer pattern.\n\n
 | 
			
		||||
@@ -376,6 +377,22 @@
 | 
			
		||||
                                  (tuple 'def bindings $iter)
 | 
			
		||||
                                  subloop
 | 
			
		||||
                                  (tuple 'set $iter (tuple next $dict $iter)))))
 | 
			
		||||
            :pairs (do
 | 
			
		||||
                     (def sym? (symbol? bindings))
 | 
			
		||||
                     (def $dict (gensym))
 | 
			
		||||
                     (def $iter (gensym))
 | 
			
		||||
                     (def preds @['and (tuple not= nil $iter)])
 | 
			
		||||
                     (def subloop (doone (+ i 3) preds))
 | 
			
		||||
                     (tuple 'do
 | 
			
		||||
                            (tuple 'def $dict object)
 | 
			
		||||
                            (tuple 'var $iter (tuple next $dict nil))
 | 
			
		||||
                            (tuple 'while (tuple/slice preds)
 | 
			
		||||
                                   (if sym?
 | 
			
		||||
                                     (tuple 'def bindings (tuple tuple $iter (tuple get $dict $iter))))
 | 
			
		||||
                                   (if-not sym? (tuple 'def (get bindings 0) $iter))
 | 
			
		||||
                                   (if-not sym? (tuple 'def (get bindings 1) (tuple get $dict $iter)))
 | 
			
		||||
                                   subloop
 | 
			
		||||
                                   (tuple 'set $iter (tuple next $dict $iter)))))
 | 
			
		||||
            :in (do
 | 
			
		||||
                  (def $len (gensym))
 | 
			
		||||
                  (def $i (gensym))
 | 
			
		||||
@@ -809,28 +826,34 @@
 | 
			
		||||
    ~(let [,sym ,last] (if ,sym ,(tuple/slice parts 0))))
 | 
			
		||||
  (reduce fop x forms))
 | 
			
		||||
 | 
			
		||||
(defn walk-ind [f form]
 | 
			
		||||
  (def len (length form))
 | 
			
		||||
  (def ret (array/new len))
 | 
			
		||||
  (each x form (array/push ret (f x)))
 | 
			
		||||
  ret)
 | 
			
		||||
 | 
			
		||||
(defn walk-dict [f form]
 | 
			
		||||
  (def ret @{})
 | 
			
		||||
  (loop [k :keys form]
 | 
			
		||||
    (put ret (f k) (f form.k)))
 | 
			
		||||
  ret)
 | 
			
		||||
 | 
			
		||||
(defn walk
 | 
			
		||||
  "Iterate over the values in ast and apply f
 | 
			
		||||
  to them. Collect the results in a data structure . If ast is not a
 | 
			
		||||
  table, struct, array, or tuple,
 | 
			
		||||
  behaves as the identity function."
 | 
			
		||||
  returns form."
 | 
			
		||||
  [f form]
 | 
			
		||||
  (defn walk-ind []
 | 
			
		||||
    (def ret @[])
 | 
			
		||||
    (each x form (array/push ret (f x)))
 | 
			
		||||
    ret)
 | 
			
		||||
  (defn walk-dict []
 | 
			
		||||
    (def ret @[])
 | 
			
		||||
    (loop [k :keys form]
 | 
			
		||||
      (array/push ret (f k) (f form.k)))
 | 
			
		||||
    ret)
 | 
			
		||||
  (case (type form)
 | 
			
		||||
    :table (table ;(walk-dict))
 | 
			
		||||
    :struct (struct ;(walk-dict))
 | 
			
		||||
    :array (walk-ind)
 | 
			
		||||
    :tuple (tuple ;(walk-ind))
 | 
			
		||||
    :table (walk-dict f form)
 | 
			
		||||
    :struct (table/to-struct (walk-dict f form))
 | 
			
		||||
    :array (walk-ind f form)
 | 
			
		||||
    :tuple (tuple/slice (walk-ind f form))
 | 
			
		||||
    form))
 | 
			
		||||
 | 
			
		||||
(put _env 'walk-ind nil)
 | 
			
		||||
(put _env 'walk-dict nil)
 | 
			
		||||
 | 
			
		||||
(defn post-walk
 | 
			
		||||
  "Do a post-order traversal of a data sructure and call (f x)
 | 
			
		||||
  on every visitation."
 | 
			
		||||
 
 | 
			
		||||
@@ -253,6 +253,8 @@ static int varleaf(
 | 
			
		||||
        const uint8_t *sym,
 | 
			
		||||
        JanetSlot s,
 | 
			
		||||
        JanetTable *attr) {
 | 
			
		||||
    if (sym[0] == ':')
 | 
			
		||||
        janetc_cerror(c, "cannot create binding to keyword symbol");
 | 
			
		||||
    if (c->scope->flags & JANET_SCOPE_TOP) {
 | 
			
		||||
        /* Global var, generate var */
 | 
			
		||||
        JanetSlot refslot;
 | 
			
		||||
@@ -287,6 +289,8 @@ static int defleaf(
 | 
			
		||||
        const uint8_t *sym,
 | 
			
		||||
        JanetSlot s,
 | 
			
		||||
        JanetTable *attr) {
 | 
			
		||||
    if (sym[0] == ':')
 | 
			
		||||
        janetc_cerror(c, "cannot create binding to keyword symbol");
 | 
			
		||||
    if (c->scope->flags & JANET_SCOPE_TOP) {
 | 
			
		||||
        JanetTable *tab = janet_table(2);
 | 
			
		||||
        janet_table_put(tab, janet_csymbolv(":source-map"),
 | 
			
		||||
 
 | 
			
		||||
@@ -53,4 +53,21 @@
 | 
			
		||||
 | 
			
		||||
(assert (= var-b "hello") "regression 1")
 | 
			
		||||
 | 
			
		||||
# Some macros
 | 
			
		||||
 | 
			
		||||
(assert (= 2 (if-not 1 3 2)) "if-not 1")
 | 
			
		||||
(assert (= 3 (if-not false 3)) "if-not 2")
 | 
			
		||||
(assert (= 3 (if-not nil 3 2)) "if-not 3")
 | 
			
		||||
(assert (= nil (if-not true 3)) "if-not 4")
 | 
			
		||||
 | 
			
		||||
(assert (= 4 (unless false (+ 1 2 3) 4)) "unless")
 | 
			
		||||
 | 
			
		||||
(def res @{})
 | 
			
		||||
(loop [[k v] :pairs @{1 2 3 4 5 6}]
 | 
			
		||||
  (put res k v))
 | 
			
		||||
(assert (and
 | 
			
		||||
          (= (get res 1) 2)
 | 
			
		||||
          (= (get res 3) 4)
 | 
			
		||||
          (= (get res 5) 6)) "loop :pairs")
 | 
			
		||||
 | 
			
		||||
(end-suite)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user