mirror of
https://github.com/janet-lang/janet
synced 2024-12-23 06:50:26 +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:
parent
00f0f628e8
commit
7880d73201
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user