mirror of
				https://github.com/janet-lang/janet
				synced 2025-10-31 15:43:01 +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
	 Calvin Rose
					Calvin Rose