mirror of
https://github.com/janet-lang/janet
synced 2024-06-25 22:53:16 +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
|
## 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">
|
<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
|
(defmacro if-not
|
||||||
"Shorthand for (if (not ... "
|
"Shorthand for (if (not ... "
|
||||||
[condition exp-1 exp-2]
|
[condition exp-1 exp-2 &]
|
||||||
~(if ,condition ,exp-2 ,exp-1))
|
~(if ,condition ,exp-2 ,exp-1))
|
||||||
|
|
||||||
(defmacro when
|
(defmacro when
|
||||||
|
@ -290,6 +290,7 @@
|
||||||
\t:range - loop over a range. The object should be two element tuple with a start
|
\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
|
and end value. The range is half open, [start, end).\n
|
||||||
\t:keys - Iterate over the keys in a data structure.\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: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
|
\t:generate - Iterate over values yielded from a fiber. Can be paired with the generator
|
||||||
function for the producer/consumer pattern.\n\n
|
function for the producer/consumer pattern.\n\n
|
||||||
|
@ -376,6 +377,22 @@
|
||||||
(tuple 'def bindings $iter)
|
(tuple 'def bindings $iter)
|
||||||
subloop
|
subloop
|
||||||
(tuple 'set $iter (tuple next $dict $iter)))))
|
(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
|
:in (do
|
||||||
(def $len (gensym))
|
(def $len (gensym))
|
||||||
(def $i (gensym))
|
(def $i (gensym))
|
||||||
|
@ -809,28 +826,34 @@
|
||||||
~(let [,sym ,last] (if ,sym ,(tuple/slice parts 0))))
|
~(let [,sym ,last] (if ,sym ,(tuple/slice parts 0))))
|
||||||
(reduce fop x forms))
|
(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
|
(defn walk
|
||||||
"Iterate over the values in ast and apply f
|
"Iterate over the values in ast and apply f
|
||||||
to them. Collect the results in a data structure . If ast is not a
|
to them. Collect the results in a data structure . If ast is not a
|
||||||
table, struct, array, or tuple,
|
table, struct, array, or tuple,
|
||||||
behaves as the identity function."
|
returns form."
|
||||||
[f 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)
|
(case (type form)
|
||||||
:table (table ;(walk-dict))
|
:table (walk-dict f form)
|
||||||
:struct (struct ;(walk-dict))
|
:struct (table/to-struct (walk-dict f form))
|
||||||
:array (walk-ind)
|
:array (walk-ind f form)
|
||||||
:tuple (tuple ;(walk-ind))
|
:tuple (tuple/slice (walk-ind f form))
|
||||||
form))
|
form))
|
||||||
|
|
||||||
|
(put _env 'walk-ind nil)
|
||||||
|
(put _env 'walk-dict nil)
|
||||||
|
|
||||||
(defn post-walk
|
(defn post-walk
|
||||||
"Do a post-order traversal of a data sructure and call (f x)
|
"Do a post-order traversal of a data sructure and call (f x)
|
||||||
on every visitation."
|
on every visitation."
|
||||||
|
|
|
@ -253,6 +253,8 @@ static int varleaf(
|
||||||
const uint8_t *sym,
|
const uint8_t *sym,
|
||||||
JanetSlot s,
|
JanetSlot s,
|
||||||
JanetTable *attr) {
|
JanetTable *attr) {
|
||||||
|
if (sym[0] == ':')
|
||||||
|
janetc_cerror(c, "cannot create binding to keyword symbol");
|
||||||
if (c->scope->flags & JANET_SCOPE_TOP) {
|
if (c->scope->flags & JANET_SCOPE_TOP) {
|
||||||
/* Global var, generate var */
|
/* Global var, generate var */
|
||||||
JanetSlot refslot;
|
JanetSlot refslot;
|
||||||
|
@ -287,6 +289,8 @@ static int defleaf(
|
||||||
const uint8_t *sym,
|
const uint8_t *sym,
|
||||||
JanetSlot s,
|
JanetSlot s,
|
||||||
JanetTable *attr) {
|
JanetTable *attr) {
|
||||||
|
if (sym[0] == ':')
|
||||||
|
janetc_cerror(c, "cannot create binding to keyword symbol");
|
||||||
if (c->scope->flags & JANET_SCOPE_TOP) {
|
if (c->scope->flags & JANET_SCOPE_TOP) {
|
||||||
JanetTable *tab = janet_table(2);
|
JanetTable *tab = janet_table(2);
|
||||||
janet_table_put(tab, janet_csymbolv(":source-map"),
|
janet_table_put(tab, janet_csymbolv(":source-map"),
|
||||||
|
|
|
@ -53,4 +53,21 @@
|
||||||
|
|
||||||
(assert (= var-b "hello") "regression 1")
|
(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)
|
(end-suite)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user