diff options
| author | 2024-12-26 18:45:02 -0500 | |
|---|---|---|
| committer | 2024-12-26 18:45:02 -0500 | |
| commit | e46c3aa157f7c02fc8488dab462463ad982eebd3 (patch) | |
| tree | 8ed18e9a993b4343a680679f2bd4f382b3328596 | |
| parent | move cond-thunk test to test folder (diff) | |
README: add basic tutorial
| -rw-r--r-- | README.md | 92 |
1 files changed, 92 insertions, 0 deletions
@@ -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 |
