1
0
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:
Calvin Rose 2018-12-29 17:23:31 -05:00
parent 00f0f628e8
commit 7880d73201
5 changed files with 208 additions and 16 deletions

View File

@ -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
View 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

View File

@ -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."

View File

@ -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"),

View File

@ -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)