Peter McGoron ae436e505b | ||
---|---|---|
doc | ||
tests | ||
.gitignore | ||
COPYING | ||
Makefile | ||
README.md | ||
cond-thunk.egg | ||
mcgoron.cond-thunk.scm | ||
mcgoron.cond-thunk.sld | ||
mcgoron.cond-thunk.srfi.210.compat.sld | ||
mcgoron.cond-thunk.values.scm | ||
mcgoron.cond-thunk.values.sld |
README.md
COND-THUNK
License: Apache-2.0
cond-thunk
is a library that abstracts conditionals into procedures
called conditional procedues (ct). This allows the programmer to
abstract conditional branches into procedures or macros, allowing for
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