Macros for abstracting conditional branches and an extension to cond-values
Go to file
Peter McGoron ae436e505b evaluate-destructor-to-boolean 2025-01-09 16:21:39 -05:00
doc evaluate-destructor-to-boolean 2025-01-09 16:21:39 -05:00
tests evaluate-destructor-to-boolean 2025-01-09 16:21:39 -05:00
.gitignore generalize generated files 2024-12-29 17:40:57 -05:00
COPYING cond-thunk 2024-12-26 12:35:51 -05:00
Makefile generalize generated files 2024-12-29 17:40:57 -05:00
README.md README: add basic tutorial 2024-12-26 18:45:02 -05:00
cond-thunk.egg evaluate-destructor-to-boolean 2025-01-09 16:21:39 -05:00
mcgoron.cond-thunk.scm evaluate-thunk-to-boolean 2025-01-09 15:59:48 -05:00
mcgoron.cond-thunk.sld evaluate-thunk-to-boolean 2025-01-09 15:59:48 -05:00
mcgoron.cond-thunk.srfi.210.compat.sld values lib 2024-12-26 18:04:58 -05:00
mcgoron.cond-thunk.values.scm evaluate-destructor-to-boolean 2025-01-09 16:21:39 -05:00
mcgoron.cond-thunk.values.sld evaluate-destructor-to-boolean 2025-01-09 16:21:39 -05:00

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