aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Peter McGoron 2024-12-26 18:45:02 -0500
committerGravatar Peter McGoron 2024-12-26 18:45:02 -0500
commite46c3aa157f7c02fc8488dab462463ad982eebd3 (patch)
tree8ed18e9a993b4343a680679f2bd4f382b3328596
parentmove cond-thunk test to test folder (diff)
README: add basic tutorial
-rw-r--r--README.md92
1 files changed, 92 insertions, 0 deletions
diff --git a/README.md b/README.md
index 52aba39..560452c 100644
--- a/README.md
+++ b/README.md
@@ -9,3 +9,95 @@ multiple value binding or backtracking.
Documentation for this library is in the format of index.scheme.org and
is located in `doc`.
+
+## Tutorial -- `cond-thunk`
+
+This tutorial is to get you aquainted with the basic `cond-thunk` form.
+
+The `cond-thunk` macro
+
+ (cond-thunk
+ expr1 expr2 ...
+ (else body ...))
+
+evaluates each expression in order. If the expression is `#f`, it
+evaluates the next expression. If it is a thunk, then it tail-calls
+the thunk. If no expression returns a thunk, then the `body ...` is
+evaluated for its values.
+
+We will start by rewriting `map` using `cond-thunk`.
+`map` in regular Scheme is
+
+ (define (map f lst)
+ (cond
+ ((null? lst) '())
+ ((pair? lst) (cons (f (car lst)) (map f (cdr lst))))
+ (else (error "not a list" lst))))
+
+With `cond-thunk`, this becomes
+
+ (define (map f lst)
+ (cond-thunk
+ (if (null? lst)
+ (lambda () '())
+ #f)
+ (if (pair? lst)
+ (lambda ()
+ (cons (f (car lst)) (map f (cdr lst))))
+ #f)
+ (else (error "not a list" lst))))
+
+This is very verbose. We can use the helper macro `when-ct` which will
+handle the conditional and wrapping the body in a lambda:
+
+ (define (map f lst)
+ (cond-thunk
+ (when-ct (null? lst) '())
+ (when-ct (pair? lst)
+ (cons (f (car lst)) (map f (cdr lst))))
+ (else (error "not a list" lst))))
+
+At this point, we seem to have just re-made the usual `map`, but with
+more macro invocations. But now each branch is it's own expression, which
+are not limited by the definition of the `cond` macro.
+
+Let's implement `filter`:
+
+ (define (filter predicate? lst)
+ (cond-thunk
+ (when-ct (null? lst) '())
+ (when-ct (not (pair? lst))
+ (error "not a list" lst))
+ (when-ct (predicate? (car lst))
+ (cons (car lst) (filter predicate? (cdr lst))))
+ (else (filter predicate? (cdr lst)))))
+
+There is a common case between `filter` and `map`: the empty list and the
+error check. We can abstract them into their own procedures:
+
+ (define (null-on-null lst)
+ (when-ct (null? lst) '()))
+
+ (define (not-a-list-error lst)
+ (when-ct (not (pair? lst))
+ (error "not a list" lst)))
+
+ (define (map f lst)
+ (cond-thunk
+ (null-on-null lst)
+ (not-a-list-error lst)
+ (else (cons (f (car lst)) (map f (cdr lst))))))
+
+ (define (filter predicate? lst)
+ (cond-thunk
+ (null-on-null lst)
+ (not-a-list-error lst)
+ (when-ct (predicate? (car lst))
+ (cons (car lst) (filter predicate? (cdr lst))))
+ (else (filter predicate? (cdr lst)))))
+
+This makes the clauses more declarative, and easier to modify.
+
+## Tutorial -- `after`
+
+TODO