Copyright 2014-2018 — William Emerison Six

All rights reserved

Distributed under LGPL 2.1 or Apache 2.0

Dedication

For Mom and Dad. Thanks for everything.

Preface

This is a book about compiler design for people who have no interest in studying compiler design. …Umm, then who wants to read this book? Let me try this again… This book is the study of source code which is discarded by the compiler, having no representation in the generated machine code. …Ummm, still not right… This book is about viewing a compiler not only as a means of translating source code into machine code, but also viewing it as an interpreter capable of any general purpose computation. …Closer, but who cares?… I think I got it now. This is a book about "Testing at Compile-Time"!

What do I mean by that? Let’s say you’re looking at source code with which you are unfamiliar, such as the following:

(define permutations
  (lambda (l)
    (if (null? l)
        '()
        (let permutations ((l l))
          (if (null? (cdr l))
              (list l)
              (flatmap (lambda (x) (map (lambda (y) (cons x y))
                                        (permutations (remove x l))))
                       l))))))

What does the code do? How did the author intend for it to be used? In trying to answer those questions, fans of statically-typed programming languages might lament the lack of types, as types help them to reason about programs and help them to deduce where to look to find more information. In trying to answer those questions, fans of dynamically-typed languages might argue "Look at the tests!", as tests ensure the code functions in a user-specified way and they serve as a form of documentation. But where are those tests? Probably in some other file whose file-system path is similar to the current file’s path (e.g., src/com/BigCorp/HugeProject/Foo.java is tested by test/com/BigCorp/HugeProject/FooTest.java). You’d have to find the file, open the file, look through it while ignoring tests which are for other methods. Frankly, it’s too much work and it interrupts the flow of coding, at least for me.

But how else would a programmer organize tests? Well in this book, which is the implementation of a library called "libbug", tests may be specified immediately after the procedure’s definition. Should any test fail the compiler will exit in error, like a type error in a statically-typed language.

(unit-test
(satisfies?
 permutations
 '(
   (() ())
   ((1) ((1)))
   ((1 2) ((1 2)
           (2 1)))
   ((1 2 3) ((1 2 3)
             (1 3 2)
             (2 1 3)
             (2 3 1)
             (3 1 2)
             (3 2 1)))
   )))

Why does the collocation of tests with definitions matter? Towards answering the questions "what does the code do?" and "how did the author intend for it to be used?", there is neither searching through files nor guessing how the code was originally intended to be used. The fact that the tests are collocated with the procedure definition means that the reader can inspect the tests without switching between files, perhaps before looking at the procedure’s definition. And the reader may not even read the procedure at all if the tests gave him enough information to use it successfully. Should he want to understand the procedure, he can mentally apply the procedure to the tests to understand it.

Wait a second. If those tests are defined in the source code itself, won’t they be in the executable? And won’t they run every time I execute the program? That would be unacceptable as it would both increase the size of the binary and slow down the program at start up. Fortunately the answer to both questions is no, because in chapter [buglang] I show how to specify that certain code should be interpreted by the compiler instead of being compiled. Lisp implementations such as Gambit are particularly well suited for this style of programming because unevaluated Lisp code is specified using a data structure of Lisp; because the compiler is an interpreter capable of being augmented. Upon finishing compilation, the compiler has become the very program it is compiling.

1. Introduction

Libbug is Bill’s Utilities for Gambit Scheme: a "standard library" of procedures which augments Scheme’s small set of built-in procedures. Libbug provides procedures for list processing, streams, control structures, general-purpose evaluation at compile-time, and a compile-time test framework written in only 7 lines of code! Programs written using libbug optionally may be programmed in a relatively unobstructive "literate programming" style, so that a program can be read linearly in a book form.

1.1. Prerequisites

The reader is assumed to be somewhat familiar with Scheme, with Common Lisp-style macros, and with recursive design. If the book proves too difficult for you, read "Simply Scheme"
[available on-line for no cost]
or "The Little Schemer". Since libbug uses Gambit Scheme’s Common Lisp-style macros, the author recommends reading "On Lisp" <[onlisp]>
[available on-line for no cost]
. The other books listed in the bibliography, all of which inspired ideas for this book, are recommended reading but are not necessary to understand the contents of this book.

1.2. Order of Parts

This book is a "literate program", meaning that the source code of libbug is embedded within this book, and that the book is intended to be able to be read linearly. As such, new procedures defined are dependent upon procedures either defined in standard Gambit Scheme or which have already been defined earlier in libbug. In writing the book, however, it became quite apparent that the foundation upon which libbug is constructed is by far the most difficult material. Reading the book in the order which the compiler compiles the source would cause the reader to quickly get lost in the "how", before understanding "why".

As such, the ordering of the book was rearranged in an effort to keep the reader engaged and curious. The book begins with "Part 1, The Implementation of Libbug" and ends with "Part 2, Foundations Of Libbug". The Gambit compiler, however, compiles Part 2 first, then Part 1.

1.3. Conventions

Code which is part of libbug will be outlined and will have line numbers on the left.

    1: ;; This is part of libbug.

Example code which is not part of libbug will not be outlined nor will it have line numbers.

(+ 1 ("This is NOT part of libbug"))

Some examples within this book show interaction with "gsi". Such examples will look like the following:

> (+ 1 2)
3

The line on which the user entered text begins with a ">". The result of evaluating that line appears on the subsequent line. In this case, 1 added to 2 evaluates to 3.

1.4. Getting the Source Code And Building

The Scheme source code is located at http://github.com/billsix/bug. The Scheme files produce the libbug library, as well as this book. Currently the code works on various distributions of Linux, on FreeBSD, and on Mac OS X. The build currently does not work on Windows.

The prerequisites for building libbug are a C compiler
[such as GCC]
, Autoconf, Automake, and Gambit Scheme
[http://gambitscheme.org]
version 4.8 or newer.

To compile the book and library, execute the following on the command line:

$ ./autogen.sh
$ ./configure --prefix=$BUG_HOME --enable-html
$ make
$ make install
  • The argument to "prefix" is the location into which libbug will be installed when "make install" is executed. "$BUG_HOME" is an environment variable that I have not defined, so the reader should substitute "$BUG_HOME" with an actual filesystem path.

  • "--enable-html" means to build this book as a HTML via asciidoc. To disable the creation of the html, substitute "--enable-html=no".

After installing libbug, you should set the following environment variables.

export PATH=$BUG_HOME/bin:$PATH
export PKG_CONFIG_PATH=$BUG_HOME/lib/pkgconfig/
export LD_LIBRARY_PATH=$BUG_HOME/lib:$LD_LIBRARY_PATH
export LIBRARY_PATH=$BUG_HOME/lib:$LIBRARY_PATH

1.5. Creating Your Own Project

$ bug-create-project testProject 1.0 "Jane Doe <jane@doe.com>"
$ cd testProject/
$ source env.sh
$ ./autogen.sh
$ ./configure --prefix=$BUILD_DIR
....
....
$ make
.....
"FIRST 10 PRIMES"
(2 3 5 7 11 13 17 19 23 29)
....
....
$ make install
....
$ cd $BUILD_DIR
$ ./bin/testProject
"FIRST 10 PRIMES"
(2 3 5 7 11 13 17 19 23 29)

Of particular note is that a "FIRST 10 PRIMES", and the 10 values, were printed during the compilation of the source code in the "make" phase.

1.6. Comparison of Compile-Time Computations in Other Languages

What exactly is computation at compile-time? An introduction to the topic is provided in Appendix [appendix1] demonstrated in languages of more widespread use (C and C++), along with a comparison of their expressive power.

The Implementation of Libbug

1. Introductory Procedures

This chapter begins the definition of libbug’s standard library of Scheme procedures and macros
[The code within chapters [beginninglibbug] through [endinglibbug] (inclusive) is found in "src/main.bug.scm".]
, along with tests which are run as part of the compilation process. If any test fails, the compiler will exit in error, much like a type error in a statically-typed language.

To gain such functionality libbug cannot be defined using Gambit Scheme’s "define", "define-macro", and "define-structure", since they only define variables and procedures for use at run-time
[well… that statement is not true for "define-macro", but it makes for a simpler explanation upon first reading]
. Instead, definitions within libbug use "libbug-private#define", "libbug-private#define-macro", and "libbug-private##define-structure"
[Per convention within libbug, procedures namespaced to "libbug-private" are not compiled into the library; such procedures are meant for private use within the implementation of libbug.]
, which are implemented in Chapter [buglang]. How they are implemented is not relevant yet, since the use of these procedure-defining procedures will be explained incrementally.

    1: (include "bug-language.scm")
    2: (##namespace ("libbug-private#" define define-macro define-structure))
  • On line 1, the code which makes computation at compile-time possible is imported. That code is defined in Chapter [buglang].

  • On line 2, Gambit’s "##namespace" procedure is invoked, ensuring that all unnamespaced uses of "define", "define-macro", and "define-structure" will use libbug’s version of those procedures instead of Gambit’s.

  • On line 3, all unnamespaced uses of "if" will use libbug’s version.

1.1. noop

The first definition is "noop" (meaning "no operation"), a procedure which takes zero arguments and which evaluates to the symbol 'noop.

    1: (define noop
    2:   (lambda () 'noop))
  • On line 1, the libbug-private#define macro
    [defined in section [libbugdefine]]
    is invoked.

  • On line 1, the variable name "noop".

  • On line 2, the lambda literal to be stored into the variable. Libbug includes a Scheme preprocessor "bug-gscpp", which expands lambda literals into lambdas. In this case, "bug-gscpp" expands

['noop]

into

(lambda () 'noop)
    1: (unit-test
    2:  (equal? (noop) 'noop))
  • On line 1, an invocation of "unit-test". In this case, "unit-test" takes one parameter, which is a test to be run at compile-time.

  • On line 2, an expression which evaluates to a boolean. This is a test which will be evaluated at compile-time. Should the test fail, the compilation of libbug will fail and neither the shared library nor the document which you are currently reading will be created. Tests are not present in the created library.

"noop" does not look useful at first glance, but it is used when a procedure of zero arguments is required but the resulting value of it is not. For instance, "noop" is used as a default "exception-handler" for many procedures within libbug.

1.2. identity

"identity" is a procedure of one argument which evaluates to its argument. <[calculi]>

    1: (define identity
    2:   (lambda (x) x))

"unit-test" can take more than one test as parameters.

    1: (unit-test
    2:  (equal? "foo" (identity "foo"))
    3:  (equal? identity (identity identity)))

1.3. all?

Like regular Scheme’s "and", but takes a list instead of a variable number of arguments, and all elements of the list are evaluated before "all?" is applied.

    1: (define all?
    2:   (lambda (l)
    3:     (if (null? l)
    4:         #t
    5:         (if (car l)
    6:             (all? (cdr l))
    7:             #f))))
    1: (unit-test
    2:  (all? '())
    3:  (all? '(1))
    4:  (all? '(#t))
    5:  (all? '(#t #t))
    6:  (not (all? '(#f)))
    7:  (not (all? '(#t #t #t #f))))

Tests in libbug are defined for two purposes. Firstly, to ensure that the expected behavior of a procedure does not change when the source code has changed. Secondly, as a form of documentation. Libbug is unique
[as far as the author knows]
in that the tests are collocated with the procedure definitions. The reader is encouraged to read the tests for a procedure before reading the implementation; since in many cases, the tests are designed specifically to guide the reader through the implementation.

1.4. satisfies?

When writing multiple tests, why explicitly invoke the procedure repeatedly with varying inputs and outputs, as was done for "all?"? Instead, provide the procedure and a list of input/output pairs
[Within libbug, a parameter named "f" usually means the parameter is a procedure.]
.

    1: (define satisfies?
    2:   (lambda (f list-of-pairs)
    3:     (all? (map (lambda (pair) (equal? (f (car pair))
    4:                                       (cadr pair)))
    5:                list-of-pairs))))
    1: (unit-test
    2:  (satisfies?
    3:   (lambda (x) (+ x 1))
    4:   '(
    5:     (0 1)
    6:     (1 2)
    7:     (2 3)
    8:     ))
    9:  (satisfies?
   10:   all?
   11:   '(
   12:     (() #t)
   13:     ((1) #t)
   14:     ((#t) #t)
   15:     ((#t #t) #t)
   16:     ((#f) #f)
   17:     ((#t #t #t #f) #f)))
   18:  )

1.5. while

Programmers who are new to the Scheme language may be surprised that the language provides no built-in syntax for looping, such as "for" or "while". A better question is why don’t other languages provide primitives from which you can create those looping constructs yourself? "Take the red pill."
[Within libbug, a parameter named "pred?" or "p?" usually means the parameter is a predicate, meaning a procedure which returns true or false.]

    1: (define while
    2:   (lambda (pred? body)
    3:     (let while ((val 'noop))
    4:       (if (pred?)
    5:           (while (body))
    6:           val))))
    1: (unit-test
    2:  (let ((a 0))
    3:    (and (equal? (while (lambda () (< a 5))
    4:                        (lambda () (set! a (+ a 1))))
    5:                 #!void)
    6:         (equal? a 5)))
    7:  (let ((a 0))
    8:    (and (equal? (while (lambda () (< a 5))
    9:                        (lambda () (set! a (+ a 1))
   10:                                'foo))
   11:                 'foo)
   12:         (equal? a 5))))

1.6. numeric-if

A conditional expression for numbers, based on their sign. "numeric-if" uses Gambit’s keyword syntax. "ifPositive", "ifZero", and "ifNegative" are optional arguments, each with their default value as the value in the "noop" variable.

    1: (define numeric-if
    2:   (lambda (n #!key (ifPositive noop) (ifZero noop) (ifNegative noop))
    3:     (if (> n 0)
    4:         (ifPositive)
    5:         (if (= n 0)
    6:             (ifZero)
    7:             (ifNegative)))))

Keyword arguments are optionally passed to the procedure, and use the following syntax.

    1: (unit-test
    2:  (satisfies?
    3:   (lambda (n)
    4:     (numeric-if n
    5:                 ifPositive: (lambda () 'pos)
    6:                 ifZero: (lambda () 'zero)
    7:                 ifNegative: (lambda () 'neg)))
    8:   '(
    9:     (5 pos)
   10:     (0 zero)
   11:     (-5 neg)
   12:     ))
   13:  (satisfies?
   14:   (lambda (n)
   15:     (numeric-if n
   16:                 ifZero: (lambda () 'zero)))
   17:   '(
   18:     (5 noop)
   19:     (0 zero)
   20:     (-5 noop)
   21:     ))
   22:  )

1.7. atom?

    1: (define atom?
    2:   (lambda (x)
    3:     (or (number? x)
    4:         (symbol? x)
    5:         (boolean? x)
    6:         (string? x)
    7:         (char? x))))


[Within libbug, a parameter named "x" usually means the parameter can be of any type.]

    1: (unit-test
    2:  (satisfies?
    3:   atom?
    4:   '(
    5:     (1 #t)
    6:     (1/3 #t)
    7:     (a #t)
    8:     (#t #t)
    9:     (#f #t)
   10:     ("string" #t)
   11:     (#\c #t)
   12:     ((make-vector 3) #f)
   13:     (() #f)
   14:     ((a) #f)
   15:     ))
   16:  )

1.8. complement

    1: (define complement
    2:   (lambda (f)
    3:     (lambda (#!rest args)
    4:       (not (apply f args)))))
    1: (unit-test
    2:  (satisfies?
    3:   pair?
    4:   '(
    5:     (1 #f)
    6:     ((1 2) #t)
    7:     ))
    8:  (satisfies?
    9:   (complement pair?)
   10:   '(
   11:     (1 #t)
   12:     ((1 2) #f)
   13:     ))
   14:  )

2. Lists

2.1. copy

Creates a shallow copy of the list
[meaning the list structure itself is copied, but not the data to which each node points.]

[Within libbug, a parameter named "l" usually means the parameter is a list.]
.

    1: (define copy
    2:   (lambda (l)
    3:     (map identity l)))
    1: (unit-test
    2:  (let ((a '(1 2 3 4 5)))
    3:    (and (equal? a (copy a))
    4:         (not (eq? a (copy a)))))
    5:  )

For a thorough description of "equal?" vs "eq?", see <[schemeprogramanguage]>.

2.2. proper?

Tests that the last element of the list is the sentinel value "'()". Will not terminate on a circular list.

    1: (define proper?
    2:   (lambda (l)
    3:     (if (null? l)
    4:         #t
    5:         (if (pair? l)
    6:             (proper? (cdr l))
    7:             #f))))
    1: (unit-test
    2:  (satisfies?
    3:   proper?
    4:   '(
    5:     (() #t)
    6:     ((4) #t)
    7:     ((1 2) #t)
    8:     (4 #f)
    9:     ((1 2 . 5) #f)
   10:     )))

2.3. first

    1: (define first
    2:   (lambda (l #!key (onNull noop))
    3:     (if (null? l)
    4:         (onNull)
    5:         (car l))))
    1: (unit-test
    2:  (satisfies?
    3:   first
    4:   '(
    5:     (() noop)
    6:     ((1 2 3) 1)
    7:     ))
    8:  (satisfies?
    9:   (lambda (l) (first l onNull: (lambda () 5)))
   10:   '(
   11:     (() 5)
   12:     ((1 2 3) 1)
   13:     )))

2.4. but-first

    1: (define but-first
    2:   (lambda (l #!key (onNull noop))
    3:     (if (null? l)
    4:         (onNull)
    5:         (cdr l))))
    1: (unit-test
    2:  (satisfies?
    3:   but-first
    4:   '(
    5:     (() noop)
    6:     ((1 2 3) (2 3))
    7:     ))
    8:  (satisfies?
    9:   (lambda (l) (but-first l onNull: (lambda () 5)))
   10:   '(
   11:     (() 5)
   12:     ((1 2 3) (2 3))
   13:     )))

2.5. last

    1: (define last
    2:   (lambda (l #!key (onNull noop))
    3:     (if (null? l)
    4:         (onNull)
    5:         (let last ((l l))
    6:           (if (null? (cdr l))
    7:               (car l)
    8:               (last (cdr l)))))))
    1: (unit-test
    2:  (satisfies?
    3:   last
    4:   '(
    5:     (() noop)
    6:     ((1) 1)
    7:     ((2 1) 1)
    8:     ))
    9:  (satisfies?
   10:   (lambda (l) (last l onNull: (lambda () 5)))
   11:   '(
   12:     (() 5)
   13:     ((2 1) 1)
   14:     )))

2.6. but-last

    1: (define but-last
    2:   (lambda (l #!key (onNull noop))
    3:     (if (null? l)
    4:         (onNull)
    5:         (let but-last ((l l))
    6:           (if (null? (cdr l))
    7:               '()
    8:               (cons (car l)
    9:                     (but-last (cdr l))))))))
    1: (unit-test
    2:  (satisfies?
    3:   but-last
    4:   '(
    5:     (() noop)
    6:     ((1) ())
    7:     ((2 1) (2))
    8:     ((3 2 1) (3 2))
    9:     ))
   10:  (satisfies?
   11:   (lambda (l) (but-last l onNull: (lambda () 5)))
   12:   '(
   13:     (() 5)
   14:     ((3 2 1) (3 2))
   15:     ))
   16:  )

2.7. filter

    1: (define filter
    2:   (lambda (p? l)
    3:     (let filter ((l l))
    4:       (if (null? l)
    5:           '()
    6:           (let ((first (car l)))
    7:             (if (p? first)
    8:                 (cons first (filter (cdr l)))
    9:                 (filter (cdr l))))))))

<[ss]>
[Simply Scheme has an excellent discussion on section on Higher-Order Functions and their combinations <[ss]>]
. <[sicp]>.

    1: (unit-test
    2:  (satisfies?
    3:   (lambda (l) (filter (lambda (x) (not (= 4 x)))
    4:                       l))
    5:   '(
    6:     (() ())
    7:     ((4) ())
    8:     ((1 4) (1))
    9:     ((4 1 4) (1))
   10:     ((2 4 1 4) (2 1))
   11:     )))

2.8. remove

    1: (define remove
    2:   (lambda (x l)
    3:     (filter (lambda (y) (not (equal? x y)))
    4:             l)))
    1: (unit-test
    2:  (satisfies?
    3:   (lambda (l) (remove 5 l))
    4:   '(
    5:     ((1 5 2 5 3 5 4 5 5) (1 2 3 4))
    6:     )))

2.9. fold-left

Reduce the list to a scalar by applying the reducing procedure repeatedly, starting from the "left" side of the list
[Within libbug, a parameter named "acc" usually means the parameter is an accumulated value.]
.

    1: (define fold-left
    2:   (lambda (f acc l)
    3:     (let fold-left ((acc acc) (l l))
    4:       (if (null? l)
    5:           acc
    6:           (fold-left (f acc
    7:                         (car l))
    8:                      (cdr l))))))
    1: (unit-test
    2:  (satisfies?
    3:   (lambda (l) (fold-left + 5 l))
    4:   '(
    5:     (() 5)
    6:     ((1) 6)
    7:     ((1 2) 8)
    8:     ((1 2 3 4 5 6) 26)
    9:     ))

Understanding the first test may give the reader false confidence in understanding "fold-left". To understand how "fold-left" really works, understand how it works with non-commutative procedures, such as "-".

    1:  (satisfies?
    2:   (lambda (l) (fold-left - 5 l))
    3:   '(
    4:     (() 5)
    5:     ((1) 4)
    6:     ((1 2) 2)
    7:     ((1 2 3 4 5 6) -16))))

2.10. sum

    1: (define sum
    2:   (lambda (l)
    3:     (fold-left + 0 l)))
    1: (unit-test
    2:  (satisfies?
    3:   sum
    4:   '(( (1)    1)
    5:     ( (1 2)  3)
    6:     ( (1 2 3) 6)))
    7:  )

2.11. fold-right

Reduces the list to a scalar by applying the reducing procedure repeatedly, starting from the "right" side of the list

    1: (define fold-right
    2:   (lambda (f acc l)
    3:     (let fold-right ((l l))
    4:       (if (null? l)
    5:           acc
    6:           (f (car l)
    7:              (fold-right (cdr l)))))))
    1: (unit-test
    2:  (satisfies?
    3:   (lambda (l) (fold-right + 5 l))
    4:   '(
    5:     (() 5)
    6:     ((1) 6)
    7:     ((1 2) 8)
    8:     ((1 2 3 4 5 6) 26)
    9:     ))
   10:  (satisfies?
   11:   (lambda (l) (fold-right - 5 l))
   12:   '(
   13:     (() 5)
   14:     ((1) -4)
   15:     ((1 2) 4)
   16:     ((1 2 3 4 5 6) 2)))
   17:  )

2.12. scan-left

Like "fold-left", but every intermediate value of "fold-left"s accumulator is an element in the resulting list of "scan-left".

    1: (define scan-left
    2:   (lambda (f acc l)
    3:     (let ((acc-list (list acc)))
    4:       (let scan-left ((acc acc)
    5:                       (l l)
    6:                       (last-cell acc-list))
    7:         (if (null? l)
    8:             acc-list
    9:             (let ((newacc (f acc
   10:                              (car l))))
   11:               (scan-left newacc
   12:                          (cdr l)
   13:                          (begin
   14:                            (set-cdr! last-cell (list newacc))
   15:                            (cdr last-cell)))))))))
    1: (unit-test
    2:  (satisfies?
    3:   (lambda (l) (scan-left + 5 l))
    4:   '(
    5:     (() (5))
    6:     ((1) (5 6))
    7:     ((1 2) (5 6 8))
    8:     ((1 2 3 4 5 6) (5 6 8 11 15 20 26))
    9:     ))
   10:  (satisfies?
   11:   (lambda (l) (scan-left - 5 l))
   12:   '(
   13:     (() (5))
   14:     ((1) (5 4))
   15:     ((1 2) (5 4 2))
   16:     ((1 2 3 4 5 6) (5 4 2 -1 -5 -10 -16))))
   17:  (satisfies?
   18:   (lambda (l) (scan-left * 1 l))
   19:   '(
   20:     (() (1))
   21:     ((2) (1 2))
   22:     ((2 3) (1 2 6))
   23:     ((2 3 4) (1 2 6 24))
   24:     ((2 3 4 5 ) (1 2 6 24 120))
   25:     ))
   26:  )

2.13. append!

Like Scheme’s "append", but recycles the last cons cell, so it is a more efficient computation at the expense of mutating the input.

    1: (define append!
    2:   (lambda (#!rest ls)
    3:     (let ((append! (lambda (second-list first-list)
    4:                      (if (null? first-list)
    5:                          second-list
    6:                          (let ((head first-list))
    7:                            (let append! ((first-list first-list))
    8:                              (if (null? (cdr first-list))
    9:                                  (set-cdr! first-list second-list)
   10:                                  (append! (cdr first-list))))
   11:                            head)))))
   12:       (fold-left append! '() (reverse ls)))))
    1: (unit-test
    2:  (equal? (append! '()
    3:                   '(5))
    4:          '(5))
    5:  (equal? (append! '(1 2 3)
    6:                   '(5))
    7:          '(1 2 3 5))
    8:  (let ((a '(1 2 3))
    9:        (b '(4 5 6)))
   10:    (append! a b '(7))
   11:    (equal? a '(1 2 3 4 5 6 7)))
   12:  (let ((a '(1 2 3))
   13:        (b '(4 5 6)))
   14:    (append! a b '(7) '(8))
   15:    (equal? a '(1 2 3 4 5 6 7 8)))
   16:  )

2.14. flatmap

    1: (define flatmap
    2:   (lambda (f l)
    3:     (fold-left append! '() (map f l))))
    1: (unit-test
    2:  (satisfies?
    3:   (lambda (l) (flatmap (lambda (x) (list x
    4:                                          (+ x 1)
    5:                                          (+ x 2)))
    6:                        l))
    7:   '(
    8:     ((10 20) (10 11 12 20 21 22))
    9:     ))
   10:  )

Mutating cons cells which were created in this procedure still respects referential-transparency from the caller’s point of view.

2.15. take

    1: (define take
    2:   (lambda (n l)
    3:     (if (or (null? l)
    4:             (<= n 0))
    5:         '()
    6:         (cons (car l)
    7:               (take (- n 1)
    8:                     (cdr l))))))
    1: (unit-test
    2:  (satisfies?
    3:   (lambda (n) (take n '(a b)))
    4:   '(
    5:     (-1 ())
    6:     (0 ())
    7:     (1 (a))
    8:     (2 (a b))
    9:     (3 (a b))
   10:     )))

2.16. take-while

    1: (define take-while
    2:   (lambda (p? l)
    3:     (let ((not-p? (complement p?)))
    4:       (let take-while ((l l))
    5:         (if (or (null? l)
    6:                 (not-p? (car l)))
    7:             '()
    8:             (cons (car l)
    9:                   (take-while (cdr l))))))))
    1: (unit-test
    2:  (satisfies?
    3:   (lambda (x) (take-while (lambda (y) (not (equal? x y)))
    4:                           '(a b c)))
    5:   '(
    6:     (a ())
    7:     (b (a))
    8:     (c (a b))
    9:     (d (a b c))
   10:     )))

2.17. drop

    1: (define drop
    2:   (lambda (n l)
    3:     (if (or (null? l)
    4:             (<= n 0))
    5:         l
    6:         (drop (- n 1)
    7:               (cdr l)))))
    1: (unit-test
    2:  (satisfies?
    3:   (lambda (n) (drop n '(a b)))
    4:   '(
    5:     (-1 (a b))
    6:     (0 (a b))
    7:     (1 (b))
    8:     (2 ())
    9:     (3 ())
   10:     )))

2.18. drop-while

    1: (define drop-while
    2:   (lambda (p? l)
    3:     (let ((not-p? (complement p?)))
    4:       (let drop-while ((l l))
    5:         (if (or (null? l)
    6:                 (not-p? (car l)))
    7:             l
    8:             (drop-while (cdr l)))))))
    1: (unit-test
    2:  (satisfies?
    3:   (lambda (x) (drop-while (lambda (y) (not (equal? x y)))
    4:                           '(a b c)))
    5:   '(
    6:     (a (a b c))
    7:     (b (b c))
    8:     (c (c))
    9:     (d ())
   10:     (e ())
   11:     )))

2.19. enumerate-interval

    1: (define enumerate-interval
    2:   (lambda (low high #!key (step 1))
    3:     (let enumerate-interval ((low low))
    4:       (if (> low high)
    5:           '()
    6:           (cons low
    7:                 (enumerate-interval (+ low step)))))))
    1: (unit-test
    2:  (equal? (enumerate-interval 1 10)
    3:          '(1 2 3 4 5 6 7 8 9 10))
    4:  (equal? (enumerate-interval 1 10 step: 2)
    5:          '(1 3 5 7 9)))

2.20. any?

    1: (define any?
    2:   (lambda (l)
    3:     (if (null? l)
    4:         #f
    5:         (if (car l)
    6:             #t
    7:             (any? (cdr l))))))
    1: (unit-test
    2:  (satisfies?
    3:   any?
    4:   '(
    5:     (() #f)
    6:     ((1) #t)
    7:     ((#t) #t)
    8:     ((#t #t) #t)
    9:     ((#f) #f)
   10:     ((#t #t #t #f) #t)))
   11:  )

2.21. zip

    1: (define zip
    2:   (lambda (#!rest lsts)
    3:     (let zip ((lsts lsts))
    4:       (if (any? (map null? lsts))
    5:           '()
    6:           (cons (map car lsts)
    7:                 (zip (map cdr lsts)))))))
    1: (unit-test
    2:  (equal? (zip '() '())
    3:          '())
    4:  (equal? (zip '(1) '(4))
    5:          '((1 4)))
    6:  (equal? (zip '(1 2) '(4 5))
    7:          '((1 4)
    8:            (2 5)))
    9:  (equal? (zip '(1 2 3) '(4 5 6))
   10:          '((1 4)
   11:            (2 5)
   12:            (3 6)))
   13:  (equal? (zip '(1) '())
   14:          '())
   15:  (equal? (zip '() '(1))
   16:          '())
   17:  )
    1: (unit-test
    2:  (equal? (zip '() '() '())
    3:          '())
    4:  (equal? (zip '(1 2 3)
    5:               '(4 5 6)
    6:               '(7 8 9))
    7:          '((1 4 7)
    8:            (2 5 8)
    9:            (3 6 9)))
   10:  )
    1: (unit-test
    2:  (equal? (zip '() '() '() '())
    3:          '())
    4:  (equal? (zip '(1 2 3)
    5:               '(4 5 6)
    6:               '(7 8 9)
    7:               '(10 11 12))
    8:          '((1 4 7 10)
    9:            (2 5 8 11)
   10:            (3 6 9 12)))
   11:  )

2.22. zip-with

    1: (define zip-with
    2:   (lambda (f #!rest lsts)
    3:     (let zip ((lsts lsts))
    4:       (if (any? (map null? lsts))
    5:           '()
    6:           (cons (apply f (map car lsts))
    7:                 (zip (map cdr lsts)))))))
    1: (unit-test
    2:  (equal? (zip-with +
    3:                    '()
    4:                    '())
    5:          '())
    6:  (equal? (zip-with +
    7:                    '(1)
    8:                    '(4))
    9:          '(5))
   10:  (equal? (zip-with +
   11:                    '(1 2)
   12:                    '(4 5))
   13:          '(5 7))
   14:  (equal? (zip-with +
   15:                    '(1 2 3)
   16:                    '(4 5 6))
   17:          '(5 7 9))
   18:  (equal? (zip-with +
   19:                    '(1)
   20:                    '())
   21:          '())
   22:  (equal? (zip-with +
   23:                    '()
   24:                    '(1))
   25:          '())
   26:  )
    1: (unit-test
    2:  (equal? (zip-with +
    3:                    '()
    4:                    '()
    5:                    '())
    6:          '())
    7:  (equal? (zip-with +
    8:                    '(1 2 3)
    9:                    '(4 5 6)
   10:                    '(7 8 9))
   11:          '(12 15 18))
   12:  )
    1: (unit-test
    2:  (equal? (zip-with +
    3:                    '()
    4:                    '()
    5:                    '()
    6:                    '())
    7:          '())
    8:  (equal? (zip-with +
    9:                    '(1 2 3)
   10:                    '(4 5 6)
   11:                    '(7 8 9)
   12:                    '(10 11 12))
   13:          '(22 26 30))
   14:  )

2.23. permutations

    1: (define permutations
    2:   (lambda (l)
    3:     (if (null? l)
    4:         '()
    5:         (let permutations ((l l))
    6:           (if (null? (cdr l))
    7:               (list l)
    8:               (flatmap (lambda (x) (map (lambda (y) (cons x y))
    9:                                         (permutations (remove x l))))
   10:                        l))))))
    1: (unit-test
    2:  (satisfies?
    3:   permutations
    4:   '(
    5:     (() ())
    6:     ((1) ((1)))
    7:     ((1 2) ((1 2)
    8:             (2 1)))
    9:     ((1 2 3) ((1 2 3)
   10:               (1 3 2)
   11:               (2 1 3)
   12:               (2 3 1)
   13:               (3 1 2)
   14:               (3 2 1)))
   15:     )))

Inspired by <[sicp]>, although I think they have a slight mistake in their code. Given their definition (permutations '()) evaluates to '(()), instead of '().

See also <[taocp]>

2.24. cartesian-product

    1: (define cartesian-product
    2:   (lambda (lol)
    3:     (##define cp
    4:       (lambda (lol)
    5:         (cond
    6:          ((null? (cdr lol))
    7:           (map list (car lol)))
    8:          (#t
    9:           (flatmap (lambda (x) (map (lambda (y) (cons x y))
   10:                                     (cp (cdr lol))))
   11:                    (car lol))))))
   12:     (cond ((null? lol) '())
   13:           (#t (cp lol)))))
    1: (unit-test
    2:  (equal? (cartesian-product '())
    3:          '())
    4:  (equal? (cartesian-product '((1 2 3)))
    5:          '((1) (2) (3)))
    6:  (equal? (cartesian-product '((1 2 3)
    7:                               (4 5 6)))
    8:          '((1 4)
    9:            (1 5)
   10:            (1 6)
   11:            (2 4)
   12:            (2 5)
   13:            (2 6)
   14:            (3 4)
   15:            (3 5)
   16:            (3 6)))
    1:  (equal? (cartesian-product '((1 2 3)
    2:                               (4 5 6)
    3:                               (7 8 9)))
    4:          '((1 4 7)
    5:            (1 4 8)
    6:            (1 4 9)
    7:            (1 5 7)
    8:            (1 5 8)
    9:            (1 5 9)
   10:            (1 6 7)
   11:            (1 6 8)
   12:            (1 6 9)
   13:            (2 4 7)
   14:            (2 4 8)
   15:            (2 4 9)
   16:            (2 5 7)
   17:            (2 5 8)
   18:            (2 5 9)
   19:            (2 6 7)
   20:            (2 6 8)
   21:            (2 6 9)
   22:            (3 4 7)
   23:            (3 4 8)
   24:            (3 4 9)
   25:            (3 5 7)
   26:            (3 5 8)
   27:            (3 5 9)
   28:            (3 6 7)
   29:            (3 6 8)
   30:            (3 6 9)))
   31:  )

2.25. ref-of

The inverse of list-ref.

    1: (define ref-of
    2:   (lambda (l x #!key (onMissing noop))
    3:     (if (null? l)
    4:         (onMissing)
    5:         (let ref-of ((l l)
    6:                      (index 0))
    7:           (if (equal? (car l) x)
    8:               index
    9:               (if (null? (cdr l))
   10:                   (onMissing)
   11:                   (ref-of (cdr l) (+ index 1))))))))
    1: (unit-test
    2:  (satisfies?
    3:   (lambda (x) (ref-of '(a b c d e f g) x))
    4:   '(
    5:     (z noop)
    6:     (a 0)
    7:     (b 1)
    8:     (g 6)
    9:     ))
   10:  )
    1: (unit-test
    2:  (satisfies?
    3:   (lambda (x) (ref-of '(a b c d e f g)
    4:                       x
    5:                       onMissing: (lambda () 'missing)))
    6:   '(
    7:     (z missing)
    8:     (a 0)
    9:     ))
   10:  )
    1: (unit-test
    2:  (let ((l '(a b c d e f g)))
    3:    (satisfies?
    4:     (lambda (x) (list-ref l (ref-of l x)))
    5:     '(
    6:       (a a)
    7:       (b b)
    8:       (g g)
    9:       )))
   10:  )

2.26. list-set!

    1: ;; TODO - handle case where index is too large
    2: ;; N.B this is called list-sef! instead of list-ref-set!
    3: ;;  to facilitate use by setf!, as setf! drops the -ref suffix
    4: (define list-set!
    5:   (lambda (l index val)
    6:     (if (equal? 0 index)
    7:         (set-car! l val)
    8:         (list-set! (cdr l) (- index 1) val))))
    1: (unit-test
    2:  (let ((foo '(bar baz quux)))
    3:    (list-set! foo 0 'blah)
    4:    (equal? foo '(blah baz quux)))
    5:  (let ((foo '(bar baz quux)))
    6:    (list-set! foo 1 'blah)
    7:    (equal? foo '(bar blah quux))
    8:    )
    9:  )

2.27. partition

Partitions the input list into two lists, with the criterion being whether or not the application of the procedure "p?" to each element of the input list evaluated to true or false.

    1: (define partition
    2:   (lambda (l p?)
    3:     (let partition ((l l)
    4:                     (trueList '())
    5:                     (falseList '()))
    6:       (if (null? l)
    7:           (list trueList falseList)
    8:           (let ((head (car l)))
    9:             (if (p? head)
   10:                 (partition (cdr l)
   11:                            (cons head trueList)
   12:                            falseList)
   13:                 (partition (cdr l)
   14:                            trueList
   15:                            (cons head falseList))))))))
    1: (unit-test
    2:  (satisfies?
    3:   (lambda (l) (partition l (lambda (x) (<= x 3))))
    4:   '(
    5:     (() (()
    6:          ()))
    7:     ((3 2 5 4 1) ((1 2 3)
    8:                   (4 5)))
    9:     )))

In section [dbind], "destructuring-bind" allows for a more convenient syntax when using "partition".

> (destructuring-bind (trueList falseList)
                     (partition '(3 2 5 4 1)
                                (lambda (x) (<= x 3)))
                     trueList)
(1 2 3)
> (destructuring-bind (trueList falseList)
                     (partition '(3 2 5 4 1)
                                (lambda (x) (<= x 3)))
                     falseList)
(4 5)

2.28. sort

    1: (define sort
    2:   (lambda (l comparison?)
    3:     (let sort ((l l))
    4:       (if (null? l)
    5:           '()
    6:           (let* ((current-node (car l))
    7:                  (p (partition (cdr l)
    8:                                (lambda (x) (comparison? x current-node)))))
    9:             (append! (sort (car p))
   10:                      (cons current-node
   11:                            (sort (cadr p)))))))))
    1: (unit-test
    2:  (satisfies?
    3:   (lambda (l) (sort l <))
    4:   '(
    5:     (() ())
    6:     ((1 3 2 5 4 0) (0 1 2 3 4 5))
    7:     )))

2.29. reverse!

Reverses the list more efficiently by mutating cons cells

    1: (define reverse!
    2:   (lambda (l)
    3:     (if (null? l)
    4:         '()
    5:         (let reverse! ((current-cons-cell l)
    6:                        (reversed-list '()))
    7:           (if (null? (cdr current-cons-cell))
    8:               (begin
    9:                 (set-cdr! current-cons-cell reversed-list)
   10:                 current-cons-cell)
   11:               (let ((rest (cdr current-cons-cell)))
   12:                 (set-cdr! current-cons-cell reversed-list)
   13:                 (reverse! rest current-cons-cell)))))))
    1: (unit-test
    2:  (satisfies?
    3:   reverse!
    4:   '(
    5:     (() ())
    6:     ((1) (1))
    7:     ((2 1) (1 2))
    8:     ((3 2 1) (1 2 3))
    9:     ))
   10:  (let ((x '(1 2 3)))
   11:    (let ((y (reverse! x)))
   12:      (and (equal? y '(3 2 1))
   13:           (equal? x '(1)))))
   14:  )

3. Lifting

From the Haskell wiki
[https://wiki.haskell.org/Lifting]
"lifting is a concept which allows you to transform a function into a corresponding function within another (usually more general) setting".

3.1. string-lift-list

Strings are sequences of characters, just as lists are sequences of arbitrary Scheme objects. "string-lift-list" allows the creation of a context in which strings may be treated as lists(
[Within libbug, a parameter named "s" usually means the parameter is of type string.]
.

    1: (define string-lift-list
    2:   (lambda (f)
    3:     (lambda (#!rest s)
    4:       (list->string
    5:        (apply f
    6:               (map string->list s))))))
    7: 

3.2. string-reverse

    1: (define string-reverse
    2:   (string-lift-list reverse!))
    3: 
    1: (unit-test
    2:  (satisfies?
    3:   string-reverse
    4:   '(
    5:     ("" "")
    6:     ("foo" "oof")
    7:     ("bar" "rab")
    8:     ))
    9:  )

3.3. string-take

    1: (define string-take
    2:   (lambda (n s)
    3:     (let ((string-take-n (string-lift-list (lambda (l) (take n l)))))
    4:       (string-take-n s))))
    5: 
    1: (unit-test
    2:  (satisfies?
    3:   (lambda (s) (string-take 2 s))
    4:   '(
    5:     ("" "")
    6:     ("foo" "fo")
    7:     ))
    8:  )

3.4. string-drop

    1: (define string-drop
    2:   (lambda (n s)
    3:     (let ((string-drop-n (string-lift-list (lambda (l) (drop n l)))))
    4:       (string-drop-n s))))
    5: 
    1: (unit-test
    2:  (satisfies?
    3:   (lambda (s) (string-drop 2 s))
    4:   '(
    5:     ("" "")
    6:     ("foo" "o")
    7:     ("foobar" "obar")
    8:     ))
    9:  )

3.5. character-lift-integer

Characters are stored as integer values in computers, but in Scheme they are not treated as numbers. "character-lift-integer" allows the creation of a context in which the characters may be treated as integers.

    1: (define character-lift-integer
    2:   (lambda (f)
    3:     (lambda (#!rest c)
    4:       (integer->char
    5:        (apply f
    6:               (map char->integer c))))))
    7: 
    1: (unit-test
    2:  (satisfies?
    3:   (character-lift-integer (lambda (i) (+ i 1)))
    4:   '(
    5:     (#\a #\b)
    6:     (#\b #\c)
    7:     (#\c #\d)
    8:     )))

3.6. string-map

    1: (define string-map
    2:   (lambda (f s)
    3:     (let ((string-map-f (string-lift-list (lambda (l) (map f l)))))
    4:       (string-map-f s))))
    5: 

The "Caesar Cipher". <[crypto]>.

    1: (unit-test
    2:  (satisfies?
    3:   (lambda (s)
    4:     (string-map (lambda (c) (let ((transform-char
    5:                                    (character-lift-integer
    6:                                     (lambda (base-char c)
    7:                                       (+ base-char
    8:                                          (modulo (+ (- c base-char)
    9:                                                     3)
   10:                                                  26))))))
   11:                               (transform-char #\a c)))
   12:                 s))
   13:   '(
   14:     ("" "")
   15:     ("abc" "def")
   16:     ("nop" "qrs")
   17:     ("xyz" "abc")
   18:     ))
   19:  )

3.7. symbol-lift-list

Symbols are sequences of characters, just as lists are sequences of arbitrary Scheme objects. "symbol-lift-list" allows the creation of a context in which the symbols may be treated as lists.

    1: (define symbol-lift-list
    2:   (lambda (f)
    3:     (lambda (#!rest sym)
    4:       (string->symbol
    5:        (apply (string-lift-list f)
    6:               (map symbol->string sym))))))
    7: 
    1: (unit-test
    2:  (satisfies?
    3:   (symbol-lift-list reverse)
    4:   '(
    5:     (foo oof)
    6:     (bar rab)
    7:     ))
    8:  (equal? ((symbol-lift-list append!) 'foo 'bar)
    9:          'foobar)
   10:  )

4. Macros

Although many concepts first implemented in Lisp (conditional expressions, garbage collection, procedures as first-class objects) have been appropriated into mainstream languages, the one feature of Lisp which remains difficult for other languages to copy is also Lisp’s best: macros. A Lisp macro is procedure for application at compile-time which takes unevaluated Lisp code as a parameter and transforms it into a new form of unevaluated code before further evaluation. Essentially, they are a facility by which a programmer may augment the compiler with new functionality while the compiler is compiling.

Transforming unevaluated code into new code introduces a few problems of which the macro writer must be aware. First, if the macro needs to create a new variable within the expanded code, the new variable must have a name, which will be generated during macro-expansion. This new name inserted into the generated code may clash with a variable name in the input form; resulting in expanded code which does not function correctly. Second, if unevaluated code which causes side-effects is inserted more than once into the generated code, the expanded code will likely have unintended side-effects from the caller of the macro’s point of view.

The first problem is solved using "gensym"s. The second problem is solved using "once-only".

For a much fuller explanation of the aforementioned problems, the author recommends reading "On Lisp" by Paul Graham <[onlisp]>.

4.1. compose

    1: (define-macro compose
    2:   (lambda (#!rest fs)
    3:     (if (null? fs)
    4:         'identity
    5:         (let* ((last-fn-is-lambda-literal
    6:                 (and (list? (last fs))
    7:                      (not (null? (last fs)))
    8:                      (equal? 'lambda
    9:                              (car (last fs)))))
   10:                (args (if last-fn-is-lambda-literal
   11:                          (cadr (last fs))
   12:                          (gensym))))
   13:           `(lambda ,(if last-fn-is-lambda-literal
   14:                         args
   15:                         `(#!rest ,args))
   16:              ,(let compose ((fs fs))
   17:                 (if (null? (cdr fs))
   18:                     (if last-fn-is-lambda-literal
   19:                         `(begin ,@(cddar fs))
   20:                         `(apply ,(car fs)
   21:                                 ,args))
   22:                     `(,(car fs)
   23:                       ,(compose (cdr fs))))))))))
  • On line 1, the "libbug-private#define-macro" macro
    [defined in section [libbugdefinemacro]]
    is invoked. Besides defining the macro, "libbug-private#define-macro" also exports the namespace definition and the macro definitions to external files, for consumption by programs which link against libbug.

    1: (unit-test
    2:  (equal? (macroexpand-1 (compose))
    3:          'identity)
    4:  (equal? ((eval (macroexpand-1 (compose))) 5)
    5:          5)
    6:  (equal? ((compose) 5)
    7:          5)
    8:  )

Macro-expansions occur during compile-time, so how should a programmer test the resulting form? Libbug provides "macroexpand-1" which treats the macro as a procedure which transforms lists into lists, and as such is able to be tested
["macroexpand-1" expands the unevaluated code passed to the macro into a new unevaluated form, which would have been compiled by the compiler if "macroexpand-1" had been absent. But, how should "gensym" evaluate, since by definition it creates symbols which cannot be typed by the programmer into a program? During the expansion of "macroexpand-1", "gensym" is overridden by a procedure which expands into typable symbols like "gensymed-var1", "gensymed-var2", etc. Each call during a macro-expansion generates a new, unique symbol. Although the generated symbol may clash with symbols in the expanded code, this does not break "gensym" for run-time evaluation, since run-time "gensym" remains not overridden. Although testing code within libbug "eval"s code generated from "macroexpand-1", the author advises against doing such in compiled code. ]
.

    1: (unit-test
    2:  (equal? (macroexpand-1 (compose (lambda (x) (* x 2))))
    3:          '(lambda (x) (begin (* x 2))))
    4:  (equal? ((eval (macroexpand-1 (compose (lambda (x) (* x 2)))))
    5:           5)
    6:          10)
    7:  (equal? ((compose (lambda (x) (* x 2)))
    8:           5)
    9:          10)
   10:  )
   11: (unit-test
   12:  (equal? (macroexpand-1 (compose (lambda (x) (+ x 1))
   13:                                  (lambda (y) (* y 2))))
   14:          '(lambda (y)
   15:             ((lambda (x) (+ x 1))
   16:              (begin (* y 2)))))
   17:  (equal? ((compose (lambda (x) (+ x 1))
   18:                    (lambda (y) (* y 2)))
   19:           5)
   20:          11)
   21:  )
   22: (unit-test
   23:  (equal? (macroexpand-1 (compose (lambda (x) (/ x 13))
   24:                                  (lambda (y) (+ y 1))
   25:                                  (lambda (z) (* z 2))))
   26:          '(lambda (z)
   27:             ((lambda (x) (/ x 13))
   28:              ((lambda (y) (+ y 1))
   29:               (begin (* z 2))))))
   30:  (equal? ((compose (lambda (x) (/ x 13))
   31:                    (lambda (y) (+ y 1))
   32:                    (lambda (z) (* z 2)))
   33:           5)
   34:          11/13)
   35:  )
   36: (unit-test
   37:  (equal? (macroexpand-1 (compose not +))
   38:          '(lambda (#!rest gensymed-var1)
   39:             (not (apply + gensymed-var1))))
   40:  (equal? ((compose not +) 1 2)
   41:          #f)
   42:  )
   43: 

4.2. aif

    1: (define-macro aif
    2:   (lambda (bool ifTrue #!rest ifFalse)
    3:     `(let ((bug#it ,bool))
    4:        (if bug#it
    5:            ,ifTrue
    6:            ,@(if (not (null? ifFalse))
    7:                  ifFalse
    8:                  (list #f))))))

Although variable capture <[onlisp]> is generally avoided, there are instances in which variable capture is desirable <[onlisp]>. Within libbug, variables intended for capture are fully qualified with a namespace to ensure that the variable is captured.

    1: (unit-test
    2:  (equal? (macroexpand-1 (aif (+ 5 10)
    3:                              (* 2 bug#it)))
    4:          '(let ((bug#it (+ 5 10)))
    5:             (if bug#it
    6:                 (* 2 bug#it)
    7:                 #f)))
    8:  (equal? (aif (+ 5 10)
    9:               (* 2 bug#it))
   10:          30)
   11:  (equal? (aif #f
   12:               (* 2 bug#it))
   13:          #f)
   14:  (equal? (aif #f
   15:               (* 2 bug#it))
   16:          #f)
   17:  (equal? (macroexpand-1 (aif #f
   18:                              (* 2 bug#it)
   19:                              5))
   20:          '(let ((bug#it #f))
   21:             (if bug#it
   22:                 (* 2 bug#it)
   23:                 5)))
   24:  (equal? (aif #f
   25:               (* 2 bug#it)
   26:               5)
   27:          5)
   28:  )

4.3. with-gensyms

"with-gensyms" is a macro to be invoked from other macros. It is a utility to minimize repetitive calls to "gensym".

    1: (define-macro with-gensyms
    2:   (lambda (symbols #!rest body)
    3:     `(let ,(map (lambda (symbol) `(,symbol (gensym)))
    4:                 symbols)
    5:        ,@body)))
    1: (unit-test
    2:  (equal? (macroexpand-1 (with-gensyms (foo bar baz)
    3:                                       `(begin
    4:                                          (pp ,foo)
    5:                                          (pp ,bar)
    6:                                          (pp ,baz))))
    7:          '(let ((foo (gensym))
    8:                 (bar (gensym))
    9:                 (baz (gensym)))
   10:             `(begin
   11:                (pp ,foo)
   12:                (pp ,bar)
   13:                (pp ,baz))))
   14:  )

4.4. once-only

Sometimes macros need to put two or more copies of an argument into the generated code. But that can cause that form to be evaluated multiple times, possibly with side-effects, which is seldom expected by the caller.

> (define-macro double (lambda (x) `(+ ,x ,x)))
> (double 5)
10

The caller of "double" should reasonably expect the argument to "double" only to be evaluated once only, because that’s how Scheme usually works.

> (define foo 5)
> (double (begin (set! foo (+ foo 1))
                foo))
13

"once-only" allows a macro-writer to ensure that a variable is evaluated only once in the generated code.

> (define-macro double (lambda (x) (once-only (x) `(+ ,x ,x))))
> (define foo 5)
> (double (begin (set! foo (+ foo 1))
                foo))
12

Like "with-gensyms", "once-only" is a macro to be used by other macros. Code which generates code which generates code. Unlike "with-gensyms", which wraps its argument with a new context to be used for later macro-expansions, "once-only" needs to defer binding the variable to a "gensym"-ed variable until the second macro-expansion. As such, it is the most difficult macro is this book.

    1: (define-macro once-only
    2:   (lambda (symbols #!rest body)
    3:     (let ((gensyms (map (lambda (s) (gensym))
    4:                         symbols)))
    5:       `(list 'let
    6:              (append ,@(map (lambda (g s) `(if (atom? ,s)
    7:                                                '()
    8:                                                (list (list (quote ,g)
    9:                                                            ,s))))
   10:                             gensyms
   11:                             symbols))
   12:              ,(append (list 'let
   13:                             (map (lambda (s g) (list s
   14:                                                      `(if (atom? ,s)
   15:                                                           ,s
   16:                                                           (quote ,g))))
   17:                                  symbols
   18:                                  gensyms))
   19:                       body)))))

"atom"s are handled as a special case to minimize the creation of "gensym"ed variables since evaluation of "atom"s causes no side effects, thus causes no problems from multiple evaluation.

4.4.1. First Macro-expansion

    1: (unit-test
    2:  (equal? (macroexpand-1 (once-only (x y) `(+ ,x ,y ,x)))
    3:          `(list 'let
    4:                 (append (if (atom? x)
    5:                             '()
    6:                             (list (list 'gensymed-var1 x)))
    7:                         (if (atom? y)
    8:                             '()
    9:                             (list (list 'gensymed-var2 y))))
   10:                 (let ((x (if (atom? x)
   11:                              x
   12:                              'gensymed-var1))
   13:                       (y (if (atom? y)
   14:                              y
   15:                              'gensymed-var2)))
   16:                   `(+ ,x ,y ,x)))))

4.4.2. The Second Macro-expansion

    1: (unit-test
    2:  (equal? (eval `(let ((x 5)
    3:                       (y 6))
    4:                   ,(macroexpand-1
    5:                     (once-only (x y)
    6:                                `(+ ,x ,y ,x)))))
    7:          `(let () (+ 5 6 5)))
    8:  (equal? (eval `(let ((x '(car foo))
    9:                       (y 6))
   10:                   ,(macroexpand-1
   11:                     (once-only (x y)
   12:                                `(+ ,x ,y ,x)))))
   13:          '(let ((gensymed-var1 (car foo)))
   14:             (+ gensymed-var1 6 gensymed-var1)))
   15:  (equal? (eval `(let ((x '(car foo))
   16:                       (y '(baz)))
   17:                   ,(macroexpand-1
   18:                     (once-only (x y)
   19:                                `(+ ,x ,y ,x)))))
   20:          '(let ((gensymed-var1 (car foo))
   21:                 (gensymed-var2 (baz)))
   22:             (+ gensymed-var1 gensymed-var2 gensymed-var1)))
   23:  )
   24: 

4.4.3. The Evaluation of the twice-expanded Code

    1: (unit-test
    2:  (equal? (eval (eval `(let ((x 5)
    3:                             (y 6))
    4:                         ,(macroexpand-1
    5:                           (once-only (x y)
    6:                                      `(+ ,x ,y ,x))))))
    7:          16)
    8:  )

5. Generalized Assignment

;;

5.1. setf!

"Rather than thinking about two distinct functions that respectively access and update a storage location somehow deduced from their arguments, we can instead simply think of a call to the access function with given arguments as a name for the storage location." <[cl]>

Create a macro named "setf!" which invokes the appropriate "setting" procedure, based on the given "accessing" procedure
[The implementation is inspired by <[setf]>.]
.

    1: (define-macro setf!
    2:   (lambda (exp val)
    3:     (if (not (pair? exp))
    4:         `(set! ,exp ,val)
    5:         (case (car exp)
    6:           ((car)  `(set-car! ,@(cdr exp) ,val))
    7:           ((cdr)  `(set-cdr! ,@(cdr exp) ,val))
    8:           ((caar) `(setf! (car (car ,@(cdr exp))) ,val))
    9:           ((cadr) `(setf! (car (cdr ,@(cdr exp))) ,val))
   10:           ((cdar) `(setf! (cdr (car ,@(cdr exp))) ,val))
   11:           ((cddr) `(setf! (cdr (cdr ,@(cdr exp))) ,val))
   12:           ((caaar) `(setf! (car (caar ,@(cdr exp))) ,val))
   13:           ((caadr) `(setf! (car (cadr ,@(cdr exp))) ,val))
   14:           ((cadar) `(setf! (car (cdar ,@(cdr exp))) ,val))
   15:           ((caddr) `(setf! (car (cddr ,@(cdr exp))) ,val))
   16:           ((cdaar) `(setf! (cdr (caar ,@(cdr exp))) ,val))
   17:           ((cdadr) `(setf! (cdr (cadr ,@(cdr exp))) ,val))
   18:           ((cddar) `(setf! (cdr (cdar ,@(cdr exp))) ,val))
   19:           ((cdddr) `(setf! (cdr (cddr ,@(cdr exp))) ,val))
   20:           ((caaaar) `(setf! (car (caaar ,@(cdr exp))) ,val))
   21:           ((caaadr) `(setf! (car (caadr ,@(cdr exp))) ,val))
   22:           ((caadar) `(setf! (car (cadar ,@(cdr exp))) ,val))
   23:           ((caaddr) `(setf! (car (caddr ,@(cdr exp))) ,val))
   24:           ((cadaar) `(setf! (car (cdaar ,@(cdr exp))) ,val))
   25:           ((cadadr) `(setf! (car (cdadr ,@(cdr exp))) ,val))
   26:           ((caddar) `(setf! (car (cddar ,@(cdr exp))) ,val))
   27:           ((cadddr) `(setf! (car (cdddr ,@(cdr exp))) ,val))
   28:           ((cdaaar) `(setf! (cdr (caaar ,@(cdr exp))) ,val))
   29:           ((cdaadr) `(setf! (cdr (caadr ,@(cdr exp))) ,val))
   30:           ((cdadar) `(setf! (cdr (cadar ,@(cdr exp))) ,val))
   31:           ((cdaddr) `(setf! (cdr (caddr ,@(cdr exp))) ,val))
   32:           ((cddaar) `(setf! (cdr (cdaar ,@(cdr exp))) ,val))
   33:           ((cddadr) `(setf! (cdr (cdadr ,@(cdr exp))) ,val))
   34:           ((cdddar) `(setf! (cdr (cddar ,@(cdr exp))) ,val))
   35:           ((cddddr) `(setf! (cdr (cdddr ,@(cdr exp))) ,val))
   36:           (else `(,(let ((append-set!
   37:                           (symbol-lift-list
   38:                            (lambda (l -set! -ref)
   39:                              (append!
   40:                               (if (equal? (reverse -ref)
   41:                                           (take 4 (reverse l)))
   42:                                   (reverse (drop 4
   43:                                                  (reverse l)))
   44:                                   l)
   45:                               -set!)))))
   46:                      (append-set! (car exp)
   47:                                   '-set!
   48:                                   '-ref))
   49:                   ,@(cdr exp)
   50:                   ,val))))))

5.1.1. Updating a Variable Directly

    1: (unit-test
    2:  (equal? (macroexpand-1
    3:           (setf! foo 10))
    4:          '(set! foo 10))
    5:  (let ((a 5))
    6:    (setf! a 10)
    7:    (equal? a 10))
    8:  )
Updating Car, Cdr, … Through Cddddr

Test updating "car".

    1: (unit-test
    2:  (equal? (macroexpand-1
    3:           (setf! (car foo) 10))
    4:          '(set-car! foo 10))
    5:  (let ((foo '(1 2)))
    6:    (setf! (car foo) 10)
    7:    (equal? (car foo) 10))
    8:  )

Test updating "cdr".

    1: (unit-test
    2:  (equal? (macroexpand-1
    3:           (setf! (cdr foo) 10))
    4:          '(set-cdr! foo 10))
    5:  (let ((foo '(1 2)))
    6:    (setf! (cdr foo) 10)
    7:    (equal? (cdr foo) 10))
    8:  (let ((foo '(bar baz quux)))
    9:    (setf! (list-ref foo 2) 'blah)
   10:    (equal? foo '(bar baz blah)))
   11:  )

Testing all of the "car" through "cddddr" procedures would be quite repetitive. Instead, create a list which has an element at each of those accessor procedures, and test each.

    1: (unit-test
    2:  (eval
    3:   `(and
    4:     ,@(map (lambda (x) `(let ((foo '((((the-caaaar)
    5:                                        the-cadaar)
    6:                                       (the-caadar)
    7:                                       ())
    8:                                      ((the-caaadr) the-cadadr)
    9:                                      (the-caaddr)
   10:                                      ()
   11:                                      )))
   12:                           (setf! (,x foo) 10)
   13:                           (equal? (,x foo) 10)))
   14:            '(car
   15:              cdr
   16:              caar cadr
   17:              cdar cddr
   18:              caaar caadr cadar caddr
   19:              cdaar cdadr cddar cdddr
   20:              caaaar caaadr caadar caaddr
   21:              cadaar cadadr caddar cadddr
   22:              cdaaar cdaadr cdadar cdaddr
   23:              cddaar cddadr cdddar cddddr
   24:              ))))
   25:  )
Suffixed By -set!

Test updating procedures where the updating procedure is the name of the getting procedure, suffixed by -set!.

    1: (at-compile-time
    2:  (##define-structure foo bar))
    3:
    4: (unit-test
    5:  (equal? (macroexpand-1
    6:           (setf! (foo-bar f) 10))
    7:          '(foo-bar-set! f 10))
    8:  (begin
    9:    (let ((f (make-foo 1)))
   10:      (setf! (foo-bar f) 10)
   11:      (equal? (make-foo 10)
   12:              f)))
   13:  )
-ref Replaced By -set!

Test updating procedures where the updating procedure is the name of the getting procedure, with the "-ref" suffix removed, replaced with "-set".

    1: (unit-test
    2:  (equal? (macroexpand-1
    3:           (setf! (string-ref s 0) #\q))
    4:          '(string-set! s 0 #\q))
    5:  (let ((s "foobar"))
    6:    (setf! (string-ref s 0) #\q)
    7:    (equal? s "qoobar"))
    8:  (equal? (macroexpand-1
    9:           (setf! (vector-ref v 2) 4))
   10:          '(vector-set! v 2 4))
   11:  (let ((v (vector 1 2 '() "")))
   12:    (setf! (vector-ref v 2) 4)
   13:    (equal? v
   14:            (vector 1 2 4 "")))
   15:  )

5.2. mutate!

Like "setf!", "mutate!" takes a generalized variable as input, but it additionally takes a procedure to be applied to the value of the generalized variable; the result of the application will be stored back into the generalized variable
["mutate!" is used in similar contexts as Common Lisp’s "define-modify-macro" would be, but it is more general, as it allows the new procedure to remain anonymous, as compared to making a new name like "toggle" <[onlisp]>.]
.

    1: (define-macro mutate!
    2:   (lambda (exp f)
    3:     (if (symbol? exp)
    4:         `(setf! ,exp (,f ,exp))
    5:         (let* ((atom-or-binding (map (lambda (x) (if (atom? x)
    6:                                                      x
    7:                                                      (list (gensym) x)))
    8:                                      (cdr exp)))
    9:                (args-of-generalized-var (map (lambda (x) (if (atom? x)
   10:                                                              x
   11:                                                              (car x)))
   12:                                              atom-or-binding)))
   13:           `(let ,(filter (complement atom?) atom-or-binding)
   14:              (setf! (,(car exp) ,@args-of-generalized-var)
   15:                     (,f (,(car exp) ,@args-of-generalized-var))))))))
    1: (unit-test
    2:  (equal? (macroexpand-1 (mutate! foo not))
    3:          '(setf! foo (not foo)))
    4:  (let ((foo #t))
    5:    (and
    6:     (begin
    7:       (mutate! foo not)
    8:       (equal? foo #f))
    9:     (begin
   10:       (mutate! foo not)
   11:       (equal? foo #t))))
   12:  )
    1: (unit-test
    2:  (equal? (macroexpand-1 (mutate! (vector-ref foo 0) (lambda (n) (+ n 1))))
    3:          '(let ()
    4:             (setf! (vector-ref foo 0)
    5:                    ((lambda (n) (+ n 1)) (vector-ref foo 0)))))
    6:  (let ((foo (vector 0 0 0)))
    7:    (mutate! (vector-ref foo 0) (lambda (n) (+ n 1)))
    8:    (equal? foo
    9:            (vector 1 0 0)))
   10:  (let ((foo (vector 0 0 0)))
   11:    (mutate! (vector-ref foo 2) (lambda (n) (+ n 1)))
   12:    (equal? foo
   13:            (vector 0 0 1)))
   14:  )
    1: (unit-test
    2:  (equal? (macroexpand-1
    3:           (mutate! (vector-ref foo (begin
    4:                                      (setf! index (+ 1 index))
    5:                                      index))
    6:                    (lambda (n) (+ n 1))))
    7:          '(let ((gensymed-var1 (begin
    8:                                  (setf! index (+ 1 index))
    9:                                  index)))
   10:             (setf! (vector-ref foo gensymed-var1)
   11:                    ((lambda (n) (+ n 1)) (vector-ref foo gensymed-var1)))))
   12:  (let ((foo (vector 0 0 0))
   13:        (index 1))
   14:    (mutate! (vector-ref foo (begin
   15:                               (setf! index (+ 1 index))
   16:                               index))
   17:             (lambda (n) (+ n 1)))
   18:    (and (equal? foo
   19:                 (vector 0 0 1))
   20:         (equal? index
   21:                 2)))
   22:  )

5.3. destructuring-bind

"destructuring-bind" is a generalization of "let", in which multiple variables may be bound to values based on their positions within a (possibly nested) list. Look at the tests at the end of the section for an example.

"destructuring-bind" is a complicated macro which can be decomposed into a regular procedure named "tree-of-accessors", and the macro "destructuring-bind"
[This poses a small problem. "tree-of-accessors" is not macroexpanded as it a not a macro, therefore it does not have access to the compile-time "gensym" procedure which allows macro-expansions to be tested. To allow "tree-of-accessors" to be tested independently, as well as part of "destructuring-bind", "tree-of-accessors" takes a procedure named "gensym" as an argument, defaulting to whatever value "gensym" is by default in the environment.]
.

    1: (define tree-of-accessors
    2:   (lambda (pat lst #!key (gensym gensym) (n 0))
    3:     (let tree-of-accessors ((pat pat)
    4:                             (lst lst)
    5:                             (n n))
    6:       (cond ((null? pat)                '())
    7:             ((symbol? pat)              `((,pat (drop ,n ,lst))))
    8:             ((equal? (car pat) '#!rest) `((,(cadr pat) (drop ,n
    9:                                                              ,lst))))
   10:             (else
   11:              (cons (let ((p (car pat)))
   12:                      (if (symbol? p)
   13:                          `(,p (list-ref ,lst ,n))
   14:                          (let ((var (gensym)))
   15:                            (cons `(,var (list-ref ,lst ,n))
   16:                                  (tree-of-accessors p
   17:                                                     var
   18:                                                     0)))))
   19:                    (tree-of-accessors (cdr pat)
   20:                                       lst
   21:                                       (+ 1 n))))))))
    1: (unit-test
    2:  (equal? (tree-of-accessors '() 'gensym-for-list)
    3:          '())
    4:  (equal? (tree-of-accessors 'a 'gensym-for-list)
    5:          '((a (drop 0 gensym-for-list))))
    6:  (equal? (tree-of-accessors '(#!rest d) 'gensym-for-list)
    7:          '((d (drop 0 gensym-for-list))))
    8:  (equal? (tree-of-accessors '(a) 'gensym-for-list)
    9:          '((a (list-ref gensym-for-list 0))))
   10:  (equal? (tree-of-accessors '(a . b) 'gensym-for-list)
   11:          '((a (list-ref gensym-for-list 0))
   12:            (b (drop 1 gensym-for-list))))
   13:  )
    1: (unit-test
    2:  (equal? (tree-of-accessors '(a (b c))
    3:                             'gensym-for-list
    4:                             gensym: (lambda () 'gensymed-var1))
    5:          '((a (list-ref gensym-for-list 0))
    6:            ((gensymed-var1 (list-ref gensym-for-list 1))
    7:             (b (list-ref gensymed-var1 0))
    8:             (c (list-ref gensymed-var1 1)))))
    9:  )

Although a call to "tree-of-accessors" by a macro could be a victim of the multiple-evaluation problem that macros may have, the only caller of "tree-of-accessors" is "destructuring-bind", which passes a "gensymed" symbol to "tree-of-accessors". Therefore "destructuring-bind" does not fall victim to unintended multiple evaluations
[Although the author would like to inline "tree-of-accessors" into the definition of "destructuring-bind", thus making it safe, he could not determine how to write tests for a nested definition.]
.

    1: (define-macro destructuring-bind
    2:   (lambda (pat lst #!rest body)
    3:     (let ((glst (gensym)))
    4:       `(let ((,glst ,lst))
    5:          ,(let create-nested-lets ((bindings
    6:                                     (tree-of-accessors pat
    7:                                                        glst
    8:                                                        gensym: gensym)))
    9:             (if (null? bindings)
   10:                 `(begin ,@body)
   11:                 `(let ,(map (lambda (b) (if (pair? (car b))
   12:                                             (car b)
   13:                                             b))
   14:                             bindings)
   15:                    ,(create-nested-lets (flatmap (lambda (b) (if (pair? (car b))
   16:                                                                  (cdr b)
   17:                                                                  '()))
   18:                                                  bindings)))))))))
    1: (unit-test
    2:  (equal? (macroexpand-1
    3:           (destructuring-bind (a (b . c) #!rest d)
    4:                               '(1 (2 3) 4 5)
    5:                               (list a b c d)))
    6:          '(let ((gensymed-var1 '(1 (2 3) 4 5)))
    7:             (let ((a (list-ref gensymed-var1 0))
    8:                   (gensymed-var2 (list-ref gensymed-var1 1))
    9:                   (d (drop 2 gensymed-var1)))
   10:               (let ((b (list-ref gensymed-var2 0))
   11:                     (c (drop 1 gensymed-var2)))
   12:                 (begin (list a b c d))))))
   13:  (equal? (destructuring-bind (a (b . c) #!rest d)
   14:                              '(1 (2 3) 4 5)
   15:                              (list a b c d))
   16:          '(1 2 (3) (4 5)))
   17:  (equal? (destructuring-bind (trueList falseList)
   18:                              (partition '(3 2 5 4 1)
   19:                                         (lambda (x) (<= x 3)))
   20:                              trueList)
   21:          '(1 2 3))
   22:  (equal? (destructuring-bind (trueList falseList)
   23:                              (partition '(3 2 5 4 1)
   24:                                         (lambda (x) (<= x 3)))
   25:                              falseList)
   26:          '(4 5))
   27:  )

6. Coroutines

6.1. end-of-generator

    1: (define end-of-generator
    2:   (lambda ()
    3:     'end-of-generator))

6.2. end-of-generator?

    1: (define end-of-generator?
    2:   (lambda (x) (equal? x (end-of-generator))))

6.3. __make-generator_\_

    1: (define __make-generator__
    2:   ;; f is a function which takes one argument, the yield procedure,
    3:   ;; which this procedure, __make-generator__, provides
    4:   (lambda (f)
    5:     ;; each instance of a generator needs two continuations.
    6:     ;; one for the generator instance, and one for the callee
    7:     ;; of the generator instance.
    8:
    9:     ;; so the definition of yield needs to have references
   10:     ;; to each of those continuations.
   11:     ;; create reference to them in  yield's environment
   12:     ;; even though they will not be bound to useful values
   13:     ;; until after yield is defined.
   14:     (##define return-to-callee-continuation 'ignore)
   15:     (##define continue-with-generator-continuation 'ignore)
   16:
   17:     ;; define the implementation "yield" for this generator
   18:     (##define yield-defined-for-this-generator-instance
   19:       ;; when yield is appied within the generator, it must
   20:       ;; return the value to the callee, but must also remember
   21:       ;; where to resume the next time the generator is applied.
   22:       ;;
   23:       ;; though, just like parameter passing in machine code,
   24:       ;; in which values must be passed on the stack/registers
   25:       ;; before control is transferred from the caller to the callee,
   26:       ;; the continuation of the generator must first be captured before
   27:       ;; invoking the callee's continuation with the yielded value.
   28:       (lambda (value-to-be-yielded)
   29:         (call/cc (lambda (yields-continuation)
   30:                    (setf! continue-with-generator-continuation yields-continuation)
   31:                    (return-to-callee-continuation value-to-be-yielded)))))
   32:
   33:
   34:     (set! continue-with-generator-continuation
   35:           (lambda (#!rest send)
   36:             ;; switch back and forth between the two routines
   37:             (f yield-defined-for-this-generator-instance)
   38:             ;; all instances of yield have been called, inform the callee
   39:             ;; that the generator is done
   40:             (return-to-callee-continuation (end-of-generator))))
   41:     ;; this is the code that is invoked every time the generator is applied.
   42:     ;; if the yield expression is defined in a context in which the evaluation
   43:     ;; of yield must evaluate to a value, then pass that value to "send".
   44:     (lambda (#!rest send)
   45:       ;; get the callee's continuation, for use when "yield" is applied
   46:       ;; within the generator
   47:       (call/cc (lambda (callees-continuation)
   48:                  ;; set the callee's continuation into "yield"'s enviroment,
   49:                  ;; so that it may be called from within yield.
   50:                  (setf! return-to-callee-continuation callees-continuation)
   51:                  ;; evaluate f up until the point that yield is invoked.
   52:
   53:                  ;; when that occurs, both return-to-callee-continuation
   54:                  ;; and continue-with-generator-continuation will be
   55:                  ;; defined correctly.
   56:                  (apply continue-with-generator-continuation send))))))
    1: (unit-test
    2:  (let ((g (__make-generator__
    3:            (lambda (yield)
    4:                (yield 'yield-value-one)
    5:                (yield 'yield-value-two)
    6:                (yield 'yield-value-three)))))
    7:    (and (equal? 'yield-value-one (g))
    8:         (equal? 'yield-value-two (g))
    9:         (equal? 'yield-value-three (g))
   10:         (end-of-generator? (g)))))

Yield may be used in a context in which a value is expected. Unlike Python, which distinguishes between these two cases by using either "next" or "send", in this library, the generator is just a regular procedure which takes 0 or 1 parameters.

    1: (unit-test
    2:  (let ((g (__make-generator__
    3:            (lambda (yield)
    4:              (let ((time 0))
    5:                (setf! time (+ time (yield 'yield-value-one)))
    6:                (yield 'yield-value-two)
    7:                (setf! time (+ time (yield 'yield-value-three)))
    8:                (yield time))))))
    9:    (and (equal? 'yield-value-one (g)) ;; just like python, nothing to send on first use
   10:         (equal? 'yield-value-two (g 10)) ;; send 10 to be the value of "(yield 'yield-value-one)"
   11:         (equal? 'yield-value-three (g)) ;;
   12:         (equal? 20 (g 10)) ;; send 10 to be the value of "(yield 'yield-value-three)"
   13:         (end-of-generator? (g))))) ;; end of the generator
   14:  ;; the generators are independent
   15:  (let ((g (__make-generator__
   16:            (lambda (yield)
   17:              (let ((time 0))
   18:                (setf! time (+ time (yield 'yield-value-one)))
   19:                (yield 'yield-value-two)
   20:                (setf! time (+ time (yield 'yield-value-three)))
   21:                (yield time)))))
   22:        (g2 (__make-generator__
   23:             (lambda (yield)
   24:               (let ((time 0))
   25:                 (setf! time (+ time (yield 'one)))
   26:                 (yield 'two)
   27:                 (setf! time (+ time (yield 'three)))
   28:                 (yield time))))))
   29:    (and (equal? 'yield-value-one (g))
   30:         (equal? 'one (g2))
   31:         (equal? 'yield-value-two (g 10))
   32:         (equal? 'two (g2 1))
   33:         (equal? 'yield-value-three (g))
   34:         (equal? 'three (g2))
   35:         (equal? 20 (g 10))
   36:         (equal? 2 (g2 1))
   37:         (end-of-generator? (g))
   38:         (end-of-generator? (g2))
   39:         ))

6.4. generator

    1: (define-macro generator
    2:   (lambda (#!rest body)
    3:     `(__make-generator__
    4:       (lambda (yield)
    5:         ,@body))))
    1: (unit-test
    2:  (begin
    3:    (let ((g (generator
    4:              (let ((time 0))
    5:                (setf! time (+ time (yield 'yield-value-one)))
    6:                (setf! time (+ time (yield 'yield-value-two)))
    7:                (setf! time (+ time (yield 'yield-value-three)))
    8:                (yield time)))))
    9:      (and (equal? 'yield-value-one (g)) ;; just like python, nothing to send on first use
   10:           (equal? 'yield-value-two (g 10)) ;; send 10 to be the value of "(yield 'yield-value-one)"
   11:           (equal? 'yield-value-three (g 10)) ;; send 10 to be the value of "(yield 'yield-value-two)"
   12:           (equal? 30 (g 10)) ;; send 10 to be the value of "(yield 'yield-value-three)"
   13:           (end-of-generator? (g 10)) ;; end of the generator)
   14:           ))))

6.5. yield-from

    1: (define-macro yield-from
    2:   (lambda (g)
    3:     (with-gensyms
    4:      (v loop)
    5:      `(let ,loop ((,v (,g)))
    6:            (if (not (end-of-generator? ,v))
    7:                (begin
    8:                  (yield ,v)
    9:                  (,loop (,g)))
   10:                 'noop)))))
    1: (unit-test
    2:  (begin
    3:    (let* ((g (generator
    4:                (yield 1)
    5:                (yield 2)
    6:                (yield 3)))
    7:           (g2 (generator
    8:                (yield 'a)
    9:                (yield 'b)
   10:                (yield 'c)))
   11:           (g3 (generator
   12:                (yield-from g)
   13:                (yield-from g2))))
   14:      (and (equal? 1 (g3))
   15:           (equal? 2 (g3))
   16:           (equal? 3 (g3))
   17:           (equal? 'a (g3))
   18:           (equal? 'b (g3))
   19:           (equal? 'c (g3))
   20:           (end-of-generator? (g3))
   21:           ))))

6.6. list→stream

Converts a list into a stream.

    1: (define list->stream
    2:   (lambda (l)
    3:     (generator
    4:      (let loop ((l l))
    5:        (if (not (null? l))
    6:            (begin
    7:              (yield (car l))
    8:              (loop (cdr l)))
    9:            'noop)))))
    1: (unit-test
    2:    (let ((g (list->stream '(1 2 3))))
    3:      (and (equal? 1 (g))
    4:           (equal? 2 (g))
    5:           (equal? 3 (g))
    6:           (end-of-generator? (g)))))

6.7. stream→list

Converts a stream into a list.

    1: (define stream->list
    2:   (lambda (s)
    3:     (let ((next (s)))
    4:       (if (end-of-generator? next)
    5:           '()
    6:           (cons next (stream->list s))))))
    1: (unit-test
    2:  (equal? (stream->list
    3:           (list->stream '(1 2 3)))
    4:          '(1 2 3))
    5:  )

6.8. integers-from

Creates an infinite
[bounded by memory constraints of course. Scheme isn’t a Turing machine.]
stream of integers.

    1: (define integers-from
    2:   (lambda (n)
    3:     (generator
    4:      (let loop ((n n))
    5:        (yield n)
    6:        (loop (+ n 1))))))
    1: (unit-test
    2:  (and
    3:    (let ((g (integers-from 0)))
    4:      (and (equal? 0 (g))
    5:           (equal? 1 (g))
    6:           (equal? 2 (g))
    7:           (equal? 3 (g))))
    8:    (let ((g (integers-from 5)))
    9:      (and (equal? 5 (g))
   10:           (equal? 6 (g))
   11:           (equal? 7 (g))
   12:           (equal? 8 (g))))))

6.9. stream-map

The analogous procedure of "map".

    1: (define stream-map
    2:   (lambda (f #!rest list-of-streams)
    3:     (generator
    4:      (let stream-map ()
    5:        (let ((the-values (map (lambda (g) (apply g '()))
    6:                               list-of-streams)))
    7:          (if (any? (map end-of-generator? the-values))
    8:              (end-of-generator)
    9:              (begin
   10:                (yield (apply f the-values))
   11:                (stream-map))))))))
    1: (unit-test
    2:  (equal? (stream->list
    3:           (stream-map (lambda (x) (+ x 1))
    4:                       (list->stream '(1 2 3 4 5))))
    5:          '(2 3 4 5 6))
    6:  (equal? (stream->list
    7:           (stream-map (lambda (x y) (+ x y))
    8:                       (list->stream '(1 2 3 4 5))
    9:                       (list->stream '(1 1 1 1 1))))
   10:          '(2 3 4 5 6))
   11:  )
   12: 

6.10. stream-filter

The analogous procedure of filter.

    1: (define stream-filter
    2:   (lambda (p? s)
    3:     (generator
    4:      (let stream-filter ()
    5:        (let ((the-value (s)))
    6:          (if (end-of-generator? the-value)
    7:              (end-of-generator)
    8:              (begin
    9:                (if (p? the-value)
   10:                    (yield the-value)
   11:                    'noop)
   12:                (stream-filter))))))))
    1: (unit-test
    2:  (equal?  (stream->list
    3:            (stream-filter (lambda (x) (not (= 4 x)))
    4:                           (list->stream '(1 4 2 4))))
    5:           '(1 2))
    6:  )

6.11. stream-take

    1: (define stream-take
    2:   (lambda (n s)
    3:     (generator
    4:      (let stream-take ((n n))
    5:        (let ((the-value (s)))
    6:          (if (or (end-of-generator? the-value)
    7:                  (<= n 0))
    8:              (end-of-generator)
    9:              (begin
   10:                (yield the-value)
   11:                (stream-take (- n 1)))))))))
    1: (unit-test
    2:  (satisfies?
    3:   (lambda (n) (stream->list
    4:                (stream-take n (integers-from 0))))
    5:   '(
    6:     (0 ())
    7:     (1 (0))
    8:     (2 (0 1))
    9:     (6 (0 1 2 3 4 5))
   10:     )))

6.12. primes

    1: (define primes
    2:   (lambda ()
    3:     (generator
    4:      (let sieve-of-eratosthenes ((s (integers-from 2)))
    5:        (let ((prime (s)))
    6:          (yield prime)
    7:          (yield-from (sieve-of-eratosthenes
    8:                       (stream-filter (lambda (n)
    9:                                        (not (equal? 0
   10:                                                     (modulo n prime))))
   11:                                      s))))))))
    1: (unit-test
    2:  (equal? (stream->list
    3:           (stream-take
    4:            10
    5:            (primes)))
    6:          '(2 3 5 7 11 13 17 19 23 29))
    7:  )

6.13. stream-drop

    1: (define stream-drop
    2:   (lambda (n s)
    3:     (let stream-drop ((n n))
    4:       (if (<= n 0)
    5:           s
    6:           (begin
    7:             (s)
    8:             (stream-drop (- n 1)))))))
    1: (unit-test
    2:  (satisfies?
    3:   (lambda (n)
    4:     (stream->list
    5:      (stream-drop n (list->stream '(a b)))))
    6:   '(
    7:     (-1 (a b))
    8:     (0 (a b))
    9:     (1 (b))
   10:     (2 ())
   11:     (3 ())
   12:     ))
   13:  (equal? (stream->list
   14:           (stream-take 10 (stream-drop 10
   15:                                        (primes))))
   16:          '(31 37 41 43 47 53 59 61 67 71))
   17:  )

6.14. stream-drop-while

    1: (define stream-drop-while
    2:   (lambda (p? s)
    3:     (let ((not-p? (complement p?)))
    4:       (generator
    5:        (let stream-drop-while ()
    6:          (let ((the-value (s)))
    7:            (if (or (end-of-generator? the-value)
    8:                    (not-p? the-value))
    9:                (begin
   10:                  (yield the-value)
   11:                  (yield-from s))
   12:                (stream-drop-while))))))))
    1: (unit-test
    2:  (satisfies?
    3:   (lambda (x)
    4:     (stream->list
    5:      (stream-drop-while (lambda (y) (not (equal? x y)))
    6:                         (list->stream
    7:                          '(a b c)))))
    8:   '(
    9:     (a (a b c))
   10:     (b (b c))
   11:     (c (c))
   12:     (d ())
   13:     (e ())
   14:     )))

6.15. stream-enumerate-interval

    1: (define stream-enumerate-interval
    2:   (lambda (low high #!key (step 1))
    3:     (generator
    4:      (let stream-enumerate-interval ((low low))
    5:        (if (> low high)
    6:            (end-of-generator)
    7:            (begin
    8:              (yield low)
    9:              (stream-enumerate-interval (+ low step))))))))
    1: (unit-test
    2:  (equal? (stream->list
    3:           (stream-enumerate-interval 1 10))
    4:          '(1 2 3 4 5 6 7 8 9 10))
    5:  (equal? (stream->list
    6:           (stream-enumerate-interval 1 10 step: 2))
    7:          '(1 3 5 7 9)))

6.16. stream-take-while

    1: (define stream-take-while
    2:   (lambda (p? s)
    3:     (let ((not-p? (complement p?)))
    4:       (generator
    5:        (let stream-take-while ()
    6:          (let ((the-value (s)))
    7:            (if (or (end-of-generator? the-value)
    8:                    (not-p? the-value))
    9:                (end-of-generator)
   10:                (begin
   11:                  (yield the-value)
   12:                  (stream-take-while)))))))))
    1: (unit-test
    2:  (satisfies?
    3:   (lambda (s)
    4:     (stream->list
    5:      (stream-take-while (lambda (n) (< n 10))
    6:                         s)))
    7:   `((,(integers-from 0)               (0 1 2 3 4 5 6 7 8 9))
    8:     (,(stream-enumerate-interval 1 4) (1 2 3 4))))
    9:  )

6.17. The End of Compilation

At the beginning of the book, in chapter [beginninglibbug], "bug-language.scm" was imported, so that "libbug-private#define", and "libbug-private#define-macro" could be used. This chapter is the end of the file "main.bug.scm". However, as will be shown in the next chapter, "bug-languge.scm" opened files for writing during compile-time, and they must be closed, accomplished by executing "at-end-of-compilation".

;;[[call-end-of-compilation

    1: (at-compile-time
    2:  (at-end-of-compilation))

Foundations Of Libbug

1. Computation At Compile-Time

This chapter
[The contents of which is found in "src/bug-language.bug.scm''.]
, which was evaluated before the previous chapters, provides the foundation for computation at compile-time. Although the most prevalent code in the previous chapters which executed at compile-time was for testing, many other computations occurred during compile-time transparently to the reader. These other computations produced output files for namespace mappings and for macro definitions, to be used by other programs which link against libbug.

1.1. at-compile-time

"at-compile-time" is a macro which "eval"s the form during macro-expansion, but evaluates to the symbol "noop", thus not affecting run-time <[evalduringmacroexpansion]>. "Eval"ing during macro-expansion is how the compiler may be augmented with new procedures, thus treating the compiler as an interpreter.

    1: (##namespace ("bug#" at-compile-time))
    2: (##define-macro at-compile-time
    3:   (lambda (#!rest forms)
    4:     (eval `(begin
    5:              ,@forms))
    6:     '(quote noop)))
  • On lines 4-5, the unevaluated code which is passed to "at-compile-time" is evaluated during macro-expansion, thus at compile-time. The macro-expansion expands into "\(quote noop\)", so the form will not evaluate at run-time.

1.2. at-both-times

"at-both-times", like "at-compile-time", "eval"s the forms in the compile-time environment, but also in the run-time environment.

    1: (##namespace ("bug#" at-both-times))
    2: (##define-macro at-both-times
    3:   (lambda (#!rest forms)
    4:     (eval `(begin
    5:              ,@forms))
    6:     `(begin
    7:        ,@forms)))
  • On lines 4-5, evaluation in the compile-time environment

  • On lines 6-7, evaluation in the run-time environment. The forms are returned unaltered to Gambit’s compiler, thus ensuring that they are defined in the run-time environment.

1.3. at-compile-time-expand

"at-compile-time-expand" allows any procedure to act as a macro.

    1: (##namespace ("bug#" at-compile-time-expand))
    2: (##define-macro at-compile-time-expand
    3:   (lambda (#!rest forms)
    4:     (eval `(begin
    5:              ,@forms))))

This allows the programmer to create "anonymous" macros.

> ((at-compile-time-expand
    (if #t
        'car
        'cdr))
  '(1 2))
1
> ((at-compile-time-expand
    (if #f
        'car
        'cdr))
  '(1 2))
(2)

1.4. Create Files for Linking Against Libbug

Libbug is a collection of procedures and macros. Building libbug results in a dynamic library and a "loadable" library (a .o1 file, for loading in the Gambit interpreter). But programs which link against libug will require libbug’s macro definitions and namespace declarations, both of which are not compiled into the library. Rather than manually copying all of them to external files, why not generate them during compile-time?

At compile time, open one file for the namespaces ("libbug#.scm") and one for the macros ("libbug-macros.scm"). These files will be pure Gambit scheme code, no libbug syntax enhancements.

    1: (at-compile-time


[All of the code through section [endOfLinkAgainstLibbug] is done at compile-time. The author chose to use subsection numbers to indicate scope for code which spans multiple pages.]

1.4.1. Create File for Namespaces

The previous three macros are currently namespaced within libbug, but external projects which link against libbug may need these namespace mappings as well. Towards that goal, open a file during compile-time and write the namespace mappings to the file.

    1:  (##define libbug-headers-file
    2:    (open-output-file '(path: "libbug#.scm" append: #f)))
    3:  (display
    4:   ";;;Copyright 2014-2018 - William Emerison Six
    5:    ;;; All rights reserved
    6:    ;;; Distributed under LGPL 2.1 or Apache 2.0
    7:    (##namespace (\"bug#\" at-compile-time))
    8:    (##namespace (\"bug#\" at-both-times))
    9:    (##namespace (\"bug#\" at-compile-time-expand))
   10:    "
   11:   libbug-headers-file)

1.4.2. Create File for Macro Definitions

The previous three macros are available within libbug, but not to programs which link against libbug. To rectify that, open a file during compile-time, and write the macro definitions to the file.

    1:  (##include "config.scm")
    2:  (##define bug-configuration#libbugsharp
    3:    (string-append bug-configuration#prefix "/include/libbug/libbug#.scm"))
    4:
    5:  (##define libbug-macros-file
    6:    (open-output-file '(path: "libbug-macros.scm" append: #f)))
    7:  (display
    8:   (string-append
    9:    ";;;Copyright 2014-2018 - William Emerison Six
   10:     ;;; All rights reserved
   11:     ;;; Distributed under LGPL 2.1 or Apache 2.0
   12:     (##include \"~~lib/gambit#.scm\")
   13:     (##include \"" bug-configuration#libbugsharp "\")
   14:     (##define-macro at-compile-time
   15:       (lambda (#!rest forms)
   16:        (eval `(begin
   17:                 ,@forms))
   18:        '(quote noop)))
   19:     (at-compile-time (##include \"" bug-configuration#libbugsharp "\"))
   20:     (##define-macro at-both-times
   21:       (lambda (#!rest forms)
   22:        (eval `(begin
   23:                 ,@forms))
   24:        `(begin
   25:           ,@forms)))
   26:     (##define-macro at-compile-time-expand
   27:       (lambda (#!rest forms)
   28:        (eval `(begin
   29:                 ,@forms))))
   30:    ")
   31:   libbug-macros-file)
  • On line 1-3, include the "config.scm" file which was preprocessed by Autoconf, so that the installation directory of libbug is known at compile-time.

  • On line 13, "libbug#.scm" is imported, so that the generated macros are namespaced correctly in external projects which import libbug. In the previous section, this file is created at compile-time. Remember that when "libbug-macros.scm" will be imported by an external project, "libbug#.scm" will exist with all of the namespaces defined in libbug
    [Marty: "Well Doc, we can scratch that idea. I mean we can’t wait around a year and a half for this thing to get finished." Doc Brown: "Marty it’s perfect, you’re just not thinking fourth-dimensionally. Don’t you see, the bridge will exist in 1985." -Back to the Future 3]
    .

1.4.3. Close Files At Compile-Time

Create a procedure to be invoked at the end of compilation, to close the compile-time generated files. Also, the namespace within the generated macro file is reset to the default namespace
[This procedure is called in section [call-end-of-compilation]]
.

    1:  (define at-end-of-compilation
    2:    (lambda ()
    3:      (display
    4:       "
    5:      (##namespace (\"\"))"
    6:       libbug-macros-file)
    7:      (force-output libbug-headers-file)
    8:      (close-output-port libbug-headers-file)
    9:      (force-output libbug-macros-file)
   10:      (close-output-port libbug-macros-file))))

1.5. libbug-private#write-and-eval

Now that those files are open, namespaces will be written to "libbug#.scm" and macro definitions to "libbug-macros.scm". However, the code shouldn’t have be to duplicated for each context, as was done for the previous three macros.

Create a macro named "write-and-eval" which will write the unevaluated form plus a newline to the file, and then return the form so that the compiler actually evaluate it
[any procedure which is namespaced to "libbug-private" is not exported to the namespace file nor the macro file]
.

    1: (##define-macro libbug-private#write-and-eval
    2:   (lambda (port form)
    3:     (eval `(begin
    4:              (write ',form ,port)
    5:              (newline ,port)))
    6:     form))

"write-and-eval" writes the form to a file, and evaluates the form only in the run-time context. For namespaces in libbug, namespaces should be valid at compile-time too.

1.6. libbug-private#namespace

Namespaces for procedures in libbug need to be available at compile-time, run-time, and in the namespace file for inclusion in projects which link to libbug.

    1: (##define-macro libbug-private#namespace
    2:   (lambda (#!rest to-namespace)
    3:     (begin
    4:       (eval `(##namespace ("bug#" ,@to-namespace)))
    5:       `(begin
    6:          (libbug-private#write-and-eval
    7:           libbug-headers-file
    8:           (##namespace ("bug#" ,@to-namespace)))))))

1.7. unit-test

Given that the reader now knows how to evaluate at compile-time, implementing a macro to execute tests at compile-time is trivial.

  • Make a macro called "unit-test", which takes an unevaluated list of tests.

  • "eval" the tests at compile-time. If any test evaluates to false, force the compiler to exit in error, producing an appropriate error message. If all of the tests pass, the Gambit compiler continues compiling subsequent definitions.

    1: (libbug-private#namespace unit-test)
    2: (libbug-private#write-and-eval
    3:  libbug-macros-file
    4:  (##define-macro unit-test
    5:    (lambda (#!rest tests)
    6:      (eval
    7:       `(if (and ,@tests)
    8:            ''noop
    9:            (begin (for-each pp '("Test Failed" ,@tests))
   10:                   (error "Tests Failed")))))))

1.8. libbug-private#define

"libbug-private#define" is the main procedure-defining procedure used throughout libbug. "libbug-private#define" takes a variable name and a value to be stored in the variable.

    1: (##define-macro
    2:   libbug-private#define
    3:   (lambda (name body)
    4:     `(begin
    5:        (libbug-private#namespace ,name)
    6:        (at-both-times
    7:         (##define ,name ,body)))))

"libbug-private#define" defines the procedure/data both at both compile-time and run-time, and exports the namespace mapping to the appropriate file. "libbug-private#define" itself is not exported to the macros file.

On line 6-7, the definition occurs at both compile-time and run-time, ensuring that the procedure is available for evaluation of tests during compile-time.

1.9. libbug-private#define-macro

Like "libbug-private#define" is built upon "define", "libbug-private#define-macro" is built upon "define-macro". "libbug-private#define-macro" ensures that the macro is available both at run-time and at compile-time. Macros do not get compiled into libraries however, so for other projects to use them they must be exported to file.

The steps will be as follows:

  • Write the macro to file

  • Write the macro-expander to file

  • Define the macro-expander within libbug

  • Define the macro.

    1: (##define-macro libbug-private#define-macro
    2:   (lambda (name lambda-value)

1.9.1. Write the Macro to File

    1:     (write
    2:      `(at-both-times
Macro Definition

The macro definition written to file will be imported as text by other projects which may have different namespace mappings than libbug. To ensure that the macro works correctly in other contexts, the appropriate namespace mappings must be loaded for the definition of this macro definition.

    1:        (##define-macro
    2:          ,name
    3:          (lambda ,(cadr lambda-value)
    4:            ,(list 'quasiquote
    5:                   `(##let ()
    6:                      (##include "~~lib/gambit#.scm")
    7:                      (##include ,bug-configuration#libbugsharp)
    8:                      ,(if (and (pair? (caddr lambda-value))
    9:                                (equal? 'quasiquote
   10:                                        (caaddr lambda-value)))
   11:                           (car (cdaddr lambda-value))
   12:                           (append (list 'unquote)
   13:                                   (cddr lambda-value)))))))
  • On line 3, the lambda value written to file shall have the same argument list as the argument list passed to "libbug-private#define-macro"

   > (cadr '(lambda (foo bar) (quasiquote 5)))
   (foo bar)
  • On line 4, the unevaluated form in argument "lambda-value" may or may not be quasiquoted. Either way, write a quasiquoted form to the file. In the case that the "lambda-value" argument was not actually intended to be quasiquoted, unquote the lambda’s body (which is done on line 12-13), thereby negating the quasi-quoting from line 4.

  • On lines 4-5, rather than nesting quasiquotes, use the technique of replacing a would-be nested quasiquote with ",(list 'quasiquote `(…)". This makes the code more readable <[paip]>. Should the reader be interested in learning more about nested quasiquotes, Appendix C of <[cl]> is a great reference.

  • On lines 5-7, ensure that the currently unevaluated form will be evaluated using libbug’s namespaces. Line 5 create a bounded context for namespace mapping. Line 6 sets standard Gambit namespace mappings, line 7 sets libbug’s mappings.

  • On line 8-10, check to see if the unevaluated form is quasiquoted.

   > (caaddr '(lambda (foo bar) (quasiquote 5)))
   quasiquote
  • On line 11, since it is quasiquoted, grab the content of the list minus the quasiquoting.

   > (car (cdaddr '(lambda (foo bar) (quasiquote 5))))
   5

Remember that this value gets wrapped in a quasiquote from line 5

   > (list 'quasiquote (car (cdaddr '(lambda (foo bar)
                                        (quasiquote 5)))))
   `5
  • On line 12-13, since this is not a quasiquoted form, just grab the form, and "unquote" it.

   > (append (list 'unquote) (cddr '(lambda (foo bar) (+ 5 5))))
   ,(+ 5 5)

Remember, this value gets wrapped in a quasiquote from line 4

   > (list 'quasiquote (append (list 'unquote)
                               (cddr '(lambda (foo bar)
                                            (+ 5 5)))))
   `,(+ 5 5)
   > (eval (list 'quasiquote (append (list 'unquote)
                                     (cddr '(lambda (foo bar)
                                               (+ 5 5)))))
   10
Define a Macro-Expander

In order to be able to test the macro-expansions effectively, a programmer needs to be able to access the code generated from the macro as a data structure. For each macro defined, create a new macro with the same name suffixed with "-expand", whose body is the same procedure as "lambda-value"s body, but the result of evaluating that body is "quoted". In this new procedure’s local environment, define "gensym" so that tests may be written
["##gensym" by definition creates a unique symbol which the programmer can not directly input, making testing of the macro-expansion impossible. Thus, the problem is solved by locally defining a new "gensym" procedure.]
.

    1:        (##define-macro
    2:          ,(string->symbol (string-append (symbol->string name)
    3:                                          "-expand"))
    4:          (lambda ,(cadr lambda-value)
    5:            (let ((gensym (let ((gensym-count 0))
    6:                            (set! gensym-count
    7:                                  (+ 1 gensym-count))
    8:                            (string->symbol
    9:                             (string-append
   10:                              "gensymed-var"
   11:                              (number->string gensym-count))))))
   12:              (list 'quote ,@(cddr lambda-value))))))

Finish writing the macro to file which was started in section [writemacrotofile].

    1:      libbug-macros-file)
    2:     (newline libbug-macros-file)

1.9.2. Define Macro and Run Tests Within Libbug

The macro has been exported to a file for use by projects which link against libbug, but it must also be defined during compilation.

    1:     `(begin

Namespace the procedure and the expander.

    1:        (libbug-private#namespace ,name)
    2:        (libbug-private#namespace ,(string->symbol
    3:                                    (string-append (symbol->string name)
    4:                                                   "-expand")))

Create the expander similarly to the previous section.

    1:        (at-both-times
    2:         (##define-macro
    3:           ,(string->symbol
    4:             (string-append (symbol->string name)
    5:                            "-expand"))
    6:           (lambda ,(cadr lambda-value)
    7:             (let ((gensym (let ((gensym-count 0))
    8:                             (lambda ()
    9:                               (set! gensym-count
   10:                                     (+ 1 gensym-count))
   11:                               (string->symbol
   12:                                (string-append
   13:                                 "gensymed-var"
   14:                                 (number->string gensym-count)))))))
   15:               (list 'quote ,@(cddr lambda-value))))))

Define the macro.

    1:        (at-both-times
    2:         (##define-macro
    3:           ,name
    4:           ,lambda-value)))))
    5: 

1.10. macroexpand-1

"macroexpand-1" is syntactic sugar which allows the programmer to test macro-expansion by writing

(equal? (macroexpand-1 (aif (+ 5 10)
                           (* 2 bug#it)))
      '(let ((bug#it (+ 5 10)))
         (if bug#it
             (* 2 bug#it)
             #f)))

instead of

(equal? (aif-expand (+ 5 10)
                   (* 2 bug#it)))
      '(let ((bug#it (+ 5 10)))
         (if bug#it
             (* 2 bug#it)
             #f)))

    1: (libbug-private#define-macro
    2:  macroexpand-1
    3:  (lambda (form)
    4:    (let* ((m (car form))
    5:           (the-expander (string->symbol
    6:                          (string-append (symbol->string m)
    7:                                         "-expand"))))
    8:      `(,the-expander ,@(cdr form)))))

1.11. libbug-private#define-structure

Like ``\#\#define-structure'', but additionally writes the namespaces
to file.
 \begin{code}
(##define-macro
  libbug-private#define-structure
  (lambda (name #!rest members)
    `(begin
       (libbug-private#namespace
        ,(string->symbol
          (string-append "make-"
                         (symbol->string name)))
        ,(string->symbol
          (string-append (symbol->string name)
                         "?"))
        ,@(map (lambda (m)
                 (string->symbol
                  (string-append (symbol->string name)
                                 "-"
                                 (symbol->string m))))
               members)
        ,@(map (lambda (m)
                 (string->symbol
                  (string-append (symbol->string name)
                                 "-"
                                 (symbol->string m)
                                 "-set!")))
               members))
       (at-both-times
        (##namespace (""
                      define
                      define-structure
                      ))
        (define-structure ,name ,@members)
        (##namespace ("libbug-private#"
                      define
                      ))
        (##namespace ("bug#"
                      define-structure
                      ))))))
//Copyright 2014-2018 - William Emerison Six
//All rights reserved
//Distributed under LGPL 2.1 or Apache 2.0

[appendix]
== Compile-Time Language
[[appendix1]]
This appendixfootnote:[Examples in the appendix will have boxes
and line numbers around the code, but they are not part of libbug.]
provides a quick tour of computer language which is interpreted
by the compiler but which is absent in the generated machine
code.  Examples are provided in
well-known languages to illustrate that
most compilers are also interpreters for a subset of the language.  This
appendix provides a baseline demonstration of compile-time computation
so that the reader may contrast these languages' capabilities with libbug's.
But first, let's discuss what is meant by the words "language","compiler", and
"interpreter".

In "Introduction to Automata Theory, Languages, and Computation", Hopcroft,
Motwani, and Ullman define language as "A set of strings all of which are chosen
from some &#931;^*^, where &#931; is a particular alphabet, is called
a language" <<<hmu2001>>>.

Plainly, that means that an "alphabet" is a set of characters (for instance, ASCII), and
that a computer "language" is defined as all of the possible sequences of characters
from that alphabet which are able to be compiled successfully.

An "interpreter" is a computer program which takes an instance of a specific
computer language as input,
and immediately executes the instructions.  A "compiler" is a computer program
which also takes an instance of a specific computer language as input,
but rather than immediately executing the input language, instead the compiler
translates the input language
into another computer language (typically machine code), which is then output to a file
for interpretationfootnote:[the Central Processing Unit (CPU) can be viewed as an
interpreter which takes machine code as its input] at a later time.

In practice though, the distinction is not binary.  Most compilers do not exclusively
translate from an input language
to an output language; instead, they also interpret a subset of the input
language as part of the compilation process.  So what
types of computations can be performed by this subset of language, and how do
they vary in expressive power?

=== C
Consider the following C code:

[source,C,linenums]

#include <stdio.h> #define square(x) x) * (x int fact(unsigned int n); int main(int argc, char* argv[]){ #ifdef DEBUG printf("Debug - argc = %d\n", argc); #endif printf("%d\n",square(fact(argc))); return 0; } int fact(unsigned int n){ return n == 0 ? 1 : n * fact(n-1); }

- On line 1, the #include preprocessor command
is language to be interpreted by the compiler,
instructing the compiler to
read the file "stdio.h"
from the filesystem and to splice the content
into the current C file.  The #include command
itself has no representation in the generated machine code, although the contents
of the included file may.

- Line 2 defines a C macro. A C macro is a procedure definition which
is to be interpreted by the compiler instead of being translated
into the output language.
A C macro takes a text
string as input and transforms it into a new text string as output.
This expansion happens before the compiler does anything
else.  For example, using GCC as a compiler, if you run the C preprocessor
"cpp" on the above C code, you'll see that

[source,C,linenums]
printf("%d\n",square(fact(argc)));
expands into

[source,C,linenums]
printf("%d\n",((fact(argc)) * (fact(argc))));
before compilation.


-Line 3 defines a procedure prototype so that
the compiler knows the argument types and the return type for a procedure not
yet defined called "fact".
It is language interpreted by the compiler to determine the types for the procedure
call to "fact" on line 8.

-Lines 4 through 10 are a procedure definition which will be
translated into instructions in the generated machine code.  Line 5 however, is language
to be interpreted by the compiler, referencing a variable which is defined
only during compile-time, to detemine whether or not line 6 should be
compiled.

=== C&#43;&#43;

C&#43;&#43; inherits C's macros, but with the additional introduction
of templates, its compile-time language
incidentally became Turing complete;  meaning that
anything that can be
calculated by a computer can be calculated using template expansion
at compile-time.  That's great!  So how does a programmer use this new
expressive power?

The following is an example of calculating the factorial of
3; using C&#43;&#43; procedures for run-time calulation, and C&#43;&#43;'s templates for compile-time
calculation.

[source,cpp,linenums]
#include <iostream>
template <unsigned int n>
struct factorial {
    enum { value = n * factorial<n - 1>::value };
};
template <>
struct factorial0 {
    enum { value = 1 };
};
int fact(unsigned int n){
  return n == 0
    ? 1
    : n * fact(n-1);
}
int main(int argc, char* argv[]){
  std::cout << factorial3::value << std::endl;
  std::cout << fact(3) << std::endl;
  return 0;
}
-Lines 10-14 are the run-time calculation of "fact", identical
to the previous version in C.


-Lines 2-9 are the
template code for the compile-time calculation of "factorial".  Notice
that the language constructs used are drastically different than the
run-time constructs.


-On line 16, "factorial3::value" is
language to be interpreted
by the compiler via template expansions.  Template expansions
conditionally match patterns based on types (or values in the case
of integers).  For iteration, templates expand recursively instead of using loops.
In this case,  "factorial3::value" expands to
"3 * factorial<3 - 1>::value".  The compiler
does the subtraction during compile-time,
so "factorial3::value" expands to
"3 * factorial2::value".
This recursion terminates on "factorial0::value"
on line 7footnote:[Even though
the base case of "factorial0" is lexically specified
after the more general
case of "factorial< n>", templates expand the most
specific case first.  So the compiler will terminate.].

-On line 17, a run-time call to "fact", defined on line 10, is declared.

==== Disassembling the Object File
The drastic difference in the generated code can be observed by using "objdump -D".

[source,txt,linenums]
400850: be 06 00 00 00   mov    $0x6,%esi
400855: bf c0 0d 60 00   mov    $0x600dc0,%edi
40085a: e8 41 fe ff ff   callq  4006a0 <_ZNSolsEi@plt>
.......
.......
.......
40086c: bf 03 00 00 00   mov    $0x3,%edi
400871: e8 a0 ff ff ff   callq  400816 <_Z4facti>
400876: 89 c6            mov    %eax,%esi
400878: bf c0 0d 60 00   mov    $0x600dc0,%edi
40087d: e8 1e fe ff ff   callq  4006a0 <_ZNSolsEi@plt>
- The instructions at memory locations 400850 through 40085a correspond to the
printing of the compile-time expanded call to factorial3::value.
The immediate value 6 is loaded into the "esi" register; then the following
two lines call the printing routinefootnote:[at least I assume, because
I don't completely understand how C&#43;&#43; name-mangling works].


- The instructions at locations 40086c through 40087d correspond to the
printing of the run-time calculation to "fact(3)".  The immediate value 3
is loaded into the "edi" register, fact is invoked, the result of
calling fact is moved from the "eax" register to the "esi" register, and then
printing routine is called.

The compile-time computation worked as expected!

=== libbug
Like C&#43;&#43;'s compile-time language, libbug's is Turing complete.  But libbug's compile-time
language is the exact same language as the run-time language!

[source,Scheme,linenums]
(at-both-times
 (define fact
   (lambda (n)
     (if (= n 0)
         1
         (* n (fact (- n 1)))))))
(pp (at-compile-time-expand (fact 3)))
(pp (fact 3))
- On line 1, the "at-both-times" macro is invoked, taking the unevaluated
definition of "fact" as
as argument, interpreting it at compile-time, and compiling it for use at runtime.


- On lines 2-5, the definition of the "fact".

- On line 7, "at-compile-time-expand" is a macro which takes unevaluated code,
evaluates it to a new form which is then compiled by the compiler.  At compile-time the code
will expand to "(pp 6)".

- On line 8, the run-time calculation of "(fact 3)".

==== Inspecting the Gambit VM Bytecode
By compiling the Scheme source to the "gvm" intermediate
representation, the previously stated behavior can be verified.

[source,txt,linenums]
  r1 = '6
  r0 = #4
  jump/safe fs=4 global[pp] nargs=1
#4 fs=4 return-point
  r1 = '3
  r0 = #5
  jump/safe fs=4 global[fact] nargs=1
#5 fs=4 return-point
  r0 = frame[1]
  jump/poll fs=4 #6
#6 fs=4
  jump/safe fs=0 global[pp] nargs=1
- Lines 1-4 correspond to "(pp (at-compile-time-expand (fact 3)))".  The precomputed
value of "(fact 3)" is 6, which is directly stored into a GVM register, and
then the "pp" routine is called to print it.

- Lines 5-12 correspond to "(pp (fact 3))".  3 is stored in a GVM register, "fact"
is called, the result of which is passed to "pp".


=== Comparison of Power
Although the compile-time languages both of C&#43;&#43; and of libbug are Turing complete,
they vary in actual real-world programming power.  The language used
for compile-time calculation of "fact" in C&#43;&#43; is a drastically different language than
the one used for run-time.  Although not fully demonstrated in this book,
C&#43;&#43; template metaprogramming relies exclusively on recursion for repetition (it has no
looping construct), it has no mutable state, and it lacks the ability to do input/output
(I/O) footnote:[For the masochist who wants to know more about the C&#43;&#43;'s compile-time language, I recommend <<<ctm>>>]

In contrast, the compile-time
language in libbug is the exact same language as the one that the compiler
is compiling, complete with state and I/O!  How can that power be used?
This book is the beginning of an answer.

[appendix]
== Acknowledgments

Thanks to Dr. Marc Feeley, for Gambit Scheme, for his mailing list postings
which inspired the foundations of this book, and for reviewing this
book.  Thanks to Adam from the Gambit mailing lists for reviewing the book,
as well as his suggestion for naming convention standards.

Thanks to Dr. John McCarthy for Lisp.

Thanks to Dr. Gerald Sussman and Dr. Guy Steele Jr for Scheme.

Thanks to Dr. Paul Graham for "On Lisp", not only for the excellent macros,
but also for demonstrating why writing well matters.

Thanks to Dr. Donald Knuth for TeX, and thanks to all contributors to
LaTeX.

Thanks to Dr. Alan Kay for Smalltalk, the first language I loved.  Lisp may be the best high-level language, but Smalltalk is the best high-level environment.

And most importantly, thanks to my wife Teresa, for everything.

[appendix]
== Related Work

- Jonathan Blow. https://www.youtube.com/watch?v=UTqZNujQOlA
- "Compile-time Unit Testing",
Aron Barath and Zoltan Porkolab, Eotvos Lorand University,
http://ceur-ws.org/Vol-1375/SQAMIA2015\_Paper1.pdf



[bibliography]
Bibliography
  • [sicp] Abelon, Harold, Gerald Jay Sussman, and Julie Sussman. Structure and Interpretation of Computer Programs, The MIT Press, Massachusetts, Second Edition, 1996.

  • [ctm] Abrahams, David and Aleksey Gurtovoy C++ Template Metaprogramming, Addison Wesley 2004.

  • [calculi] Church, Alonzo The Calculi of Lambda-Conversion, Princeton University Press, New Jersey, Second Printing, 1951.

  • [schemeprogramminglanguage] Dybvig, R. Kent. The Scheme Programming Language, The MIT Press, Massachusetts, Third Edition, 2003.

  • [evalduringmacroexpansion] Feeley, Marc. https://mercure.iro.umontreal.ca/pipermail/gambit-list/2012-April/005917.html, 2012

  • [littleschemer] Friedman, Daniel P., and Matthias Felleisen The Scheme Programming Language, The MIT Press, Massachusetts, Fourth Edition, 1996.

  • [onlisp] Graham, Paul. On Lisp, Prentice Hall, New Jersey, 1994.

  • [ansicl] Graham, Paul. ANSI Common Lisp, Prentice Hall, New Jersey, 1996.

  • [ss] Harvey, Brian and Matthew Wright. Simply Scheme - Introducing Computer Science, The MIT Press, Massachusetts, Second Edition, 2001.

  • [hmu2001] Hopcroft, John E., Rajeev Motwani, and Jeffrey D. Ullman. Introduction to Automata Theory, Languages, and Computation, Addison Wesley, Massachusetts, Second Edition, 2001.

  • [setf] Kiselyov, Oleg. http://okmij.org/ftp/Scheme/setf.txt , 1998.

  • [taocp] Knuth, Donald E. The Art Of Computer Programming, Volume 1, Addison Wesley, Massachusetts, Third Edition, 1997.

  • [paip] Norvig, Peter Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp, San Francisco, CA 1992.

  • [tapl] Pierce, Benjamin C. Types and Programming Languages, The MIT Press Cambridge, Massachusetts 2002.

  • [crypto] Stallings, William Cryptography and Network Security, Pearson Education, Upper Saddle River, New Jersey, Third Edition, 2002.

  • [cl] Steele Jr, Guy L. Common Lisp the Language, Digital Press, 1990.

2. Example Index