Showing posts with label Scheme. Show all posts
Showing posts with label Scheme. Show all posts

19 October 2023

Unit Testing Scheme

For more than four years this article has been waiting to be written. Now a rainy summer day can do wonders. Back in 2018, when I fell into Scheme, I needed a unit testing framework. I was digging into Scheme with coding exercises and wanted the quick feedback of my TDD cycle so see if I was on the right track. I did not know anything about modules, dependency management or how to get custom code in Scheme and searched the web for a small test framework. As minimalism is in the spirit of Scheme, I initially went with the Scheme Unit outlined on Cunningham's Wiki,
;
; Unit test framework for scheme
; see http://c2.com/cgi/wiki?SchemeUnit
;
(define (report-error msg)
    (display "ERROR: ")
    (display msg)
    (newline))
    
(define (assert msg b)
    (if (not b)
        (report-error msg)))
Evolution
Now that was minimalist indeed. But as good as it was, it lacked important features: I wanted more assertions, at least for all basic data types, and needed to see some colours. What is the point of a green bar, if it is not shown in green. I had created my own xUnit libraries before, one for good old Turbo Pascal and one for assembly (written in assembly - usually you would use C). While both were useful, I did not get famous for them, it seems that am focusing on "niche products" in the testing framework world. Creating my own xUnit was a kata in itself and always helped me while learning a new programming language.

I started adding more assertions already in 2015 and test cases in 2017. I kept extending it whenever I needed something new, always copying the latest file to the next code kata. In 2018 I decided to put my "SchemeUnit" up on GitHub under the name of assert-scm. Now my tests, for example the tests for the Parrot kata, look like
(include "assert.scm")
(include "parrot.scm")

(test-case "it gets the speed of an european parrot"
    (assert= 12.0
             (parrot-speed 'european-parrot 0 0.0 #f)))

(test-case "it gets the speed of an african parrot with one coconut"
    (assert= 3.0
             (parrot-speed 'african-parrot 1 0.0 #f)))
Features of xUnit
Which features are expected from a true xUnit framework? From my knowledge of JUnit, I derive the core elements of xUnit:
  • Assertions: First we need assertions. These are typically called assertSomething. Assertions are necessary to verify the actual result versus the expected one. If these values are not equal, the assertion should fail in some way. There should be assertions for equality of basic data types. In my Scheme xUnit there are (assert-true actual) and assert-false, assert= for integer numbers and symbols (and everything you can compare with = in Scheme), assert-char= and assert-string= for these primitives and assert-inexact= for floating point numbers which allows a delta for rounding errors. There are assert-null, assert-not-null, and more. As lists are the basic, all encompassing data structure in Lisp, and therefore Scheme, any testing framework for these languages needs support for comparing lists for equality: assert-list= and assert-list-deep= for flat and deep list comparison.

  • Failure Messages: Assertions need to fail with descriptive messages. For example, if two values expected and actual are not equal, I would like to see "expected: <expected value> but was: <actual value>". I hate testing frameworks which just stop with "Assertion failed." Creating good messages gets more interesting when comparing lists as they can be of different length and nested. After assertions, this is the second important thing to have.

  • Test Cases: In xUnit, test cases are often a bit weird, as they are classes containing multiple methods. Each of these methods is an individual test case because during test execution the class is instantiated for each method. In some frameworks test methods are named testSomething(), or annotations or other markers are used. In frameworks without classes, e.g. Jest or Pytest, each test function is a test case. A test case has a name, often the name of the method, some arrange code, some logic and one or more assertions. Test cases should be run independently of each other and report success or failure individually.
    (test-case "(test-case) allows several assertions"
        (assert-true #t)
        (assert-true #t))
    will print (test-case) allows several assertions .. OK.

  • Ignoring Test Cases: Sometimes I want to ignore a certain test case. Most frameworks offer ways to do that, e.g. @Ignore, @Disabled, @mark.skip or using other markers. I like the Mocha way of replacing it('') with xit('') and went for a different function ignored-test-case:
    (ignored-test-case "(ignored-test-case) is ignored, else it would fail"
        (assert-true #f))
  • Test Suites: Test suites are used to group test cases. Naturally these are Java classes, Python modules or Jest/Mocha describe blocks containing test methods. In Scheme 5 that would be files. Files can include other files which allows me to build arbitrary test suites. I rarely use test suites in any language, as I am running all tests most of the time.

  • Fixtures: Fixtures contain the necessary creation and release of resources needed for the test and make sure these are released even if the test failed. Older test frameworks allow setup and teardown methods or @Before/@After markers. Other approaches include injecting necessary dependencies, as for example JUnit 5 and Pytest do. Till now I did not need fixtures in my exercises. In small test sets, I am fine when tests stop at the first failing test.

  • Asserting on Exceptions: Few testing frameworks offer assertions for exceptions. For example in Java, before JUnit 5's assertThrows, there were 5+ ways to test that a method threw an exception. Maybe this is a special case, something that is rarely used. As I was building my assert-scm Scheme xUnit from scratch, I wanted to be sure the assertions work. How would I test for a failing assertion? I had to dig deeper into Scheme. Standard R5RS Scheme has no function to catch exceptions and different implementations handle this differently. Gambit Scheme, the Scheme I started, offers some proprietary extension for exceptions, whereas Chicken Scheme, another common Scheme, has some support for handling exceptions - called conditions. At least Chicken version 4 does, but it is outdated now. Portability is an issue between different Scheme implementations. It seems Scheme R6RS has standard support for exceptions, but its structure is way more complicated. I would like to avoid this for fun exercises.
If you are interested in features of xUnit and how to use them I recommend you work through my Unit Testing-Koans exercises.

Prime Factors
Exploring a new language is incomplete for me, until there is a nice TDD, step by step implementation of the Prime Factors kata. It is my goto exercise and I am collecting my Prime Factors solutions - which usually look the same. Here are the tests:
(include "prime-factors.scm")
(include "../assert-r5rs.scm")

(test-case "one"
    (assert-null (prime-factors 1)))

(test-case "two"
    (assert-number-list= (list 2) (prime-factors 2)))

; ...

(test-case "nine"
    (assert-number-list= (list 3 3) (prime-factors 9)))

(test-case "max"
    (assert-number-list= (list 2147483647) (prime-factors 2147483647)))
which execute in assert-scm's GitHub build,

assert-scm Prime Factors Test
and verify the code
(define (prime-factors n)
    (test-candidate n 2))

(define (test-candidate n candidate)
    (cond ((= n 1) (list))
          ((too-large? n candidate) (prime n))
          ((divides? n candidate) (keep-candidate n candidate))
          (else (next-candidate n candidate))))

(define (too-large? n candidate)
    (> candidate (sqrt n)))

(define (prime n)
    (list n))

(define (divides? n candidate)
    (= (modulo n candidate) 0))

(define (keep-candidate n candidate)
    (append (prime candidate)
            (test-candidate (/ n candidate) candidate)))

(define (next-candidate n candidate)
    (test-candidate n (+ candidate 1)))
This is neat, isn't it? If you fancy playing with Scheme and do not want to miss unit tests, check out assert-scm, a minimalist unit test framework for Scheme R5RS.

22 February 2019

Flashcard Madness: The Little Schemer

On my journey into the Scheme Programming Language I am currently reading The Little Schemer (4th edition) by Daniel P. Friedman and Matthias Felleisen. I started reading because of it was recommended by different sources. It is a fun little book. Its style is quite unusual as it is written as a series of questions, which force you reflect about the content along the way. It repeats the same steps again and again - a reminder of recursive concepts - which is forcing you to practice the concepts you just read about.

The book introduces Ten Commandments and Five Laws for the Scheme style of programming. The commandments are basic rules of working with lists and the laws define the API of used functions. Ray Grasso collected all the commandments and laws in his notes. What to you do when you have a list of rules? You study them! I like using flashcards to study short facts and created a set of digital flashcards for the Ten Commandments and Five Laws. The deck is in the Anki file format (apkg), which can be used with the Anki application under Windows and Android. There is a similar application AnkiApp for Mac OS and iOS.

The deck contains questions about the rules themselves, e.g. What is the 2nd Commandment? With answer use `cons` to build lists.. Then I derived questions which are more actionable, e.g. When recurring on a list of atoms, `lat`, ask two questions about it? With answer `(null? lat)` and `else`. I am not sure these cards are useful on their own, but they definitely helped me to commit the facts I learned from the book to my memory.

Download The_Little_Schemer.apkg here.

23 January 2019

Scheme Runtime and Editor Choices

Last month I wrote about my journey into the Scheme programming language, its standards and complete list of its special forms and functions. Today I want to describe some Scheme implementations I used as well as lightweight tooling to get started.

Scheme Runtimes
A nice thing about Scheme is its availability. There are many Scheme implementations and often many are available on different platforms. I started out using Gambit Scheme. I do not remember why I chose Gambit. It is certainly not the most popular one, which - according to questions on StackOverflow - is Racket. (On the other hand, Racked is much more than Scheme.) I guess I took what came up in one of my early Google searches.

if muybridge could bark (maybe a long shot but hey this dog is running)Gambit Scheme
Gambit is an R5RS compliant Scheme written in C. It is easy to install, mature, stable and under active development on GitHub. It is available for MacOS and Windows. Download it here. Besides the standard functions, it offers some SRFI- and many non standard ones for IO, threading and synchronisation, additional data types like signed and unsigned integer vectors (SRFI 160) and much more. Gambit comes with both a Scheme interpreter (gsi) and a compiler (gsc). The compiler transpiles Scheme into C which is in turn compiled using the GCC infrastructure (which must be in your PATH). The pre-compiled binary interpreters can be deployed as single EXE files. Yes, now I am able to write low level C code without knowing C. Win!

Gambit works well and I had no problems getting started. I recommend it to get started. While playing around with it I followed the same approach as last time and scraped the documentation for all available functions. Only later I found these lists provided by Gambit itself: R4RS and R5RS as well as all forms and functions provided by Gambit. There is a lot of documentation, which I still plan to read.

IronScheme
Then I found IronScheme. Like IronPython and IronRuby, IronScheme is a Scheme implementation based on the Microsoft DLR (i.e. the Dynamic Language Runtime). Most of its code just delegates into the CLR (i.e. the .NET Common Language Runtime). For example string-contains? is defined as
(define/contract (string-contains? str:string sub:string)
  (clr-call String Contains str sub))
IronScheme supports compilation, too. Now I can write native Windows applications without knowing anything about .NET. IronScheme is (by its own definition almost) R6RS compliant. Because of the added complexity of R6RS modularity I have not used it as all my Scheme work consists of toy projects. For real world application modularity would be the way to go.

I could not find any pre-build releases of IronScheme. To get the latest build go to its AppVeyor page, select the first build job (probably Environment: FX_VERSION=v2.0) and open the Artifacts tab. After downloading and unpacking the ZIP, the Scheme library code has to be compiled with ngen-all.cmd and echo (compile-system-libraries) ^| IronScheme.Console32-v2, yielding 10MB of DLLs.

Kawa Scheme
There are several Schemes available for the JVM (i.e. the Java Virtual Machine). One of them is The Kawa Scheme language which supports R5RS, R6RS and R7RS. I have not used Kawa. And of course there is Clojure which is a dialect of Lisp and somehow similar to Scheme. But Clojure lacks tail-call optimisation, which is required for Scheme implementations.

Mobile Devices
There are Schemes for mobile devices, too. Gambit builds a version for iPad and Android has several Schemes, e.g. Simple Scheme. So if you ever have been away from your computer and thought, "Boy, I wish I could be writing Scheme code right now" - well, now you can!

A lightweight process supports learning
I still have not read much documentation on Scheme besides SICP which uses basic language concepts so far. I am just playing. The lightweight process I am following is keeping up the fun and supports my learning:
  • Scheme has this minimalist design philosophy. I really like its simplicity. I am never stuck on syntax, reserved words or edge cases. And there are no semicolons. ;-)
  • R5RS is the Scheme to start. It is the most widely implemented standard and most documentation and StackOverflow answers apply.
  • The Scheme runtime must be easy to install and should not mess up my system.
  • I favour runtimes that are available for different architectures and operation systems so I can use the same runtime on different machines, e.g. on my main x64 workstation, my legacy x86 netbook and even my tablet. Especially when having fun with learning, the device should not be limiting me.
  • An interpreter usually starts faster than a compiler which is important for my trial and error approach. The Gambit interpreter starts fast and I use its REPL to explore the language often.
  • A lightweight editor. Emacs would be the traditional editor operating system to work with Lisp languages and I know at least one Clojure developer who uses it. I have never used it and while getting into Emacs would be a cool and fun thing to do - long overdue according to my learning list - I do not want to post phone my Scheme experience. Any text editor should do. I will talk more on editors later.
Colour Out of FocusUltraEdit Syntax Highlighting and Tool Integration
I met UltraEdit 15 years ago and still use it as my main text editor. I guess I am old-fashioned as my version is 8 years old. Before going full scale with Emacs, I wanted at least some syntax highlighting in UltraEdit. Adding a configuration for syntax highlighting is simple (and I have done it before.) With the complete list of forms and functions I created a UltraEdit wordfile for R5RS/Gambit Scheme and one for R6RS Scheme. As soon as UltraEdit knows about the structure of Scheme, it provides code completion and shows all functions defined in the current file.

In the tool configuration I declared a tool to run the current Scheme file (command line gsi "%f" with working directory %p). UltraEdit has a shortcut to select groups of parenthesis (CTRL-B) which is very handy when working with lots of parens.

The next thing I missed was auto format. Formatting is important to keep code readable. Automatic formatting is important, because it saves work and it is easy to overlook a missing blank or newline. So I created my own Scheme-Formatter.

What about a real IDE?
Eventually UltraEdit became too "small" for my Scheme project. It works well for single file scripts but I the navigation between files is limited. So I switched to Visual Studio Code with the vscode-scheme extension. Code is nice because the navigator enables fast navigation and version control is integrated. I can commit and diff without switching windows. I mostly use search and replace across all files as it is my main refactoring tool. In the end, Code is just another editor. It is not an IDE like Eclipse or IntelliJ is for Java. The Scheme extension provides syntax highlighting but the editor's auto indentation always screws up the formatting.

Conclusion
Restarting development with a new language in a new environment was amazing. As the project grew I (obviously) had to learn more and more things. For example - because of the size of the code - the next thing I wanted was to navigate to the definition of a function. Enter Ctags. Ctags supports Scheme out of the box and both UltraEdit and Code are able to use the generated tags file. Of course Emacs does as well. Maybe I need to go for Emacs after all.

20 December 2018

Scheme Programming Language

With the beginning of this year I "fell" into Scheme. I blame SoCraTes BE 2015 for that. (SoCraTes is a group of international unconferences focusing on Software Craft and Testing. They are about sustainable creation of useful software in a responsible way and usually run as self-organised Open Space.) During that SoCraTes I happened to enter a session where a small group worked on some Scheme code. The Scheme programming language is one of the two main dialects of Lisp and I recognise Lisp when I see it. The session was so much fun that it continued for the whole of the second day. We used available slots in the schedule of the open space, and in cases there were none, we continued coding Scheme in the hallway, the kitchen and other places. (Now such things only happen on SoCraTes and I encourage you to attend one if possible.)

SICP Exhibit, MIT Museum Includes a battered copy of SICPThis SoCraTes experience broke the ice with Scheme, i.e. it gave me enough exposure to a new language and enabled me to continue on my own. For the next two years I kept playing with Scheme, experimenting with code katas, e.g. the Bank-OCR Kata, porting well known exercises like Parrot, Gilded Rose and Game Of Life and I even solved some Project Euler problems using Scheme.

SICP
Earlier this year I started reading SICP. SICP stands for Structure and Interpretation of Computer Programs, a computer science text originally used in introductory courses taught at the MIT in the eighties. It had been on my reading list since 2010, when Uncle Bob recommended it in one of his Clean Coder videos. SICP had been recommended again and again and I was very happy to find the time and energy to read it.

SICP is one of the greatest books - if not the greatest book - I have ever read. It is fast paced and written in the style of all scientific books - too dense and hard to follow. Reading it is hard work. I used to like such books when I was studying applied mathematics many years ago - actually I was eating such books for breakfast ;-) The high density gives me new information fast. As I like to read books from cover to cover, I do not mind that the information is not accessible directly. Yes it is challenging to read. And it is very insightful. I appreciate most the skill of the authors to define abstractions. I am stunned again and again how they name their composed methods.

The Scheme Programming Language
SICP is strongly related to Scheme as it is one of the bibles of the Lisp/Scheme world. SICP uses Scheme for its code samples and exercises as Gerald Jay Sussman, one author of SICP, is also one of the creators of Scheme. Scheme is a version of Lisp and as such a divine language per se. It is a great language and unlike Lisp it follows a minimalist design philosophy. I really like its simplicity: There are no reserved words, not much syntax, just atoms, pairs and lists.
(define atom?
  (lambda (x)
    (and (not (pair? x))
         (not (null? x)))))
Atoms are numbers, strings, booleans, characters and symbols. Symbols i.e. names usually represent other atoms or functions. Maybe you do not like all the extra parenthesis, but it is compact and uniform. Because of the uniformity of the S-expression, it is easy to create parsers, embedded languages and even full featured Schemes, e.g. in Python, Haskell or any language. (To be fair, the last link contains implementations of Lisp not Scheme in 73 languages.)

History
As I said before Scheme is based on Lisp. The Lisp programming language was created 60 years ago by John McCarthy. And Lisp is very special, it seems to transcend the utilitarian criteria used to judge other languages. In 1975 Scheme was created by Gerald Jay Sussman and Guy Lewis Steele. Watch this great talk by Guy Steele about issues associated with designing a programming language.

Ancient HistoryStandards
One thing which confused me a lot when I started playing with Scheme were its versions. The Scheme language is standardised by IEEE and the de facto standard is called the Revised n Report on the Algorithmic Language Scheme (RnRS). The most widely implemented standard is R5RS from 1998. I use that mainly because the first Scheme implementation I downloaded was an R5RS compliant one. R5RS files use the .scm filename extension. The newer R6RS standard, ratified in 2007, is controversial because it departs from the minimalist philosophy. It introduces modularity which is breaking everything. Being used to the strong compatibility of Java or at least the major compatibility of Python I did not expect versions to be incompatible at all. R6RS files use the .ss, .sls and .sps filename extensions. The current standard is R7RS from 2013, a smaller version, defining a subset of the large R6RS version retaining the minimalism or earlier versions.

(Almost) Complete List of Special Forms and Functions
I did not read any tutorials or books on the Scheme language - I was exploring the language on my own. Still I wanted to know which library functions I could use. Similarly I used to list all classes available in each Java release. I searched for an exhaustive list of Scheme functions. There are many Schemes and I did not want to depend on any implementation specific extensions. In the end I decided to scrape the standards.

Scheme makes a difference between forms and functions. Function calls evaluate all arguments before control is passed to the function body. If some expressions should not be evaluated - as in if or switch (which is cond in Scheme) - a special form is needed. Special forms evaluate their arguments lazily. For more information see Why is cond a special form in Scheme.

R5RS Forms and Functions
I scraped the list of special forms and built-in functions from the R5RS HTML documents. The list is incomplete since I had to rely on formatting of the document. It misses define and the abbreviations like ' and @, but looks pretty good (to me) otherwise. Browsing the 193 forms and functions gives an idea of built in data types, i.e.
boolean
char
complex (number)
exact (number)
inexact (number)
list
number
pair
procedure
rational (number)
string
symbol
vector
as well as possible conversions between them available in any R5RS compliant Scheme.
char->integer
exact->inexact
inexact->exact
integer->char
list->string
list->vector
number->string
string->list
string->number
string->symbol
symbol->string
vector->list
The Leeds LibraryR6RS Forms and Functions
As mentioned earlier, R6RS is much larger than R5RS. It defines 630 forms and functions, most of them in the (new) libraries. The standard separates built-in forms and functions from the ones defined in libraries. (This is still very small, considering that the Java 8 core library contains 6000 public classes.) My list of forms and functions contains all, built-in and library alike. It looks complete, but I am not sure, I did not work with R6RS. From a quick glance R6RS adds bitwise and floating point operations as well as different types of byte vectors, enum-set and hashtable. When looking at my list now, I see that I should have scraped the names of the library modules as well. (I added that to my task list for 2019 ;-)

Scheme Requests for Implementation
Next to the RnRS standards, there are the SRFIs, a collection of concrete proposals and reference implementations. People implementing Scheme chose to implement SRFIs or not. Most Schemes support some of then, see Arthur A. Gleckler's report of SRFI support by Scheme implementations in 2018. Some Schemes have package managers which allow to download and instal packages. Usually some of those are SRFI implementations. I guess I also need to scrape these.

Conclusion
Scheme (Lisp) is so much fun, especially when you do not have to deliver anything. Most people I meet got in touch with Lisp during university but never followed up. Some are even afraid of it. Since 2016 I run Scheme coding sessions at every unconference or SoCraTes event I attend. I invite people to mob with me and I do all the typing. We always have a good time, and - after some warm up with Scheme - people really like it. They all enjoy the opportunity to dive into Lisp again. Will you?

6 November 2015

Bank-OCR Kata in Scheme

Why is it when I wanted to do something completely different from work to relax - I end doing code katas in Scheme? (Last week on Twitter.)

SoCraTes Belgium
Two weeks ago I attended SoCraTes Belgium, the Belgian branch of the Software Craftsmanship and Testing family of (un-)conferences. Being an un-conference the complete agenda of both days was created by the participants. Early during the first day, a participant proposed a session to work through the exercises of the well known SICP book using the Scheme programming language. We worked through the exercises as a mob and it was a lot of fun so another session was scheduled for afternoon. Time and again Scheme sessions were scheduled outside the official agenda. In the end I had spent several hours playing with Scheme and I really enjoyed it.

Why Scheme?
Later I started doing code katas in Scheme, which surprised me, see my initial quote. I do not know why I chose Scheme. There were other programming languages I had planned to learn. Maybe, as my friend Thomas remarked, I chose Scheme because it is seldom used in commercial projects, at least in my surroundings. As it is far away from anything I might touch during my regular work, it is easier to have fun with.

Unit Testing
Nevertheless I wanted to follow my typical development process, using TDD and looked for recommended unit testing frameworks. As minimalism is in the spirit of Scheme, I went with the Scheme Unit outlined on Cunningham's Wiki,
(define (report-error msg)
    (error (string-append "AssertionError: " msg)))

(define (assert msg b)
    (if (not b)
        (report-error msg)))
and added assertions whenever I needed them.

Banking the LightBank OCR
Today I want to share my take on the Bank OCR Kata using Scheme. The Bank OCR assignment is to parse files containing lists of numbers written in LCD style using only pipes and underscores. Each number has nine digits, all of which are in the range one to nine. I knew the Bank OCR kata and had done it before using different languages like Java or C#. I was familiar with the domain which allowed me to focus on functional programming in Scheme.

Outside-In
In the past I used the Bank OCR kata especially to practise the outside-in way of development. Using this approach you build the system from the "outside-in", following the user interaction through all the parts of the system. You start with the interactions and collaborators up-front, especially those at the top level and create fake implementations or mock necessary dependencies. With every finished component, you move to the previously mocked collaborators and implement them. See Emily Bache's article on Outside-In development for a discussion of Outside-In both with London school and classic TDD.

Outside-In vs. Functional?
So whenever I did the Bank OCR kata I tried to follow strict outside-in. But this time I wondered if the outside-in approach was feasible when using a functional language? As far as I knew the typical way of functional programming was to compose small functions to more powerful ones, which naturally lent itself to the bottom-up or classic approach. I was curious how these two would match, if at all.

The Guiding Test
Following Double Loop TDD I started with failing guiding test to parse a single number containing all possible digits,
(define all-digits (list "    _  _     _  _  _  _  _ "
                         "  | _| _||_||_ |_   ||_||_|"
                         "  ||_  _|  | _||_|  ||_| _|"
                         "                           "))

(assert-list= string=?
              "should parse a single number"
              (list "123456789")
              (bank-ocr all-digits))
which expected that bank-ocr(all-digits) yielded ["123456789"].

How to solve the problem?
Then I started to TDD the top level function bank-ocr.
(assert-list= string=?
              "should return empty list on empty input"
              (list)
              (bank-ocr (list)))
which created the initial function. Then I tested for a non-trivial case
(assert-list= string=?
              "not sure about the name of the test yet"
              (list "123456789")
              (bank-ocr all-digits))
Big Ben (London School TDD is Outside-In but Classic TDD can be as well.)But how would I solve the problem? I had no idea. Nevertheless, the first step of the algorithm was to split the input into groups of four lines each and another function, e.g. parse-line, would parse the line then. Following outside-in I defined a stub for parse-line and changed bank-ocr to call it.
;; stub
(define (parse-line ocr-line)
    "123456789")

(define (bank-ocr ocr-lines)
    (if (null? ocr-lines)
        '()
        (list (parse-line ocr-lines))))
The next test forced me to implement the recursion to call parse-line for each group of four lines.
(assert-list= string=?
              "should parse each group of lines"
              (list "123456789" "123456789")
              (bank-ocr (append all-digits all-digits)))

(define (bank-ocr ocr-lines)
    (if (null? ocr-lines)
        '()
        (append (list (parse-line (take ocr-lines 3)))
                (bank-ocr (drop ocr-lines 4)))))
Moving "in"
bank-ocr was complete but the guiding test told me that there was no parse-line function in the production code and I knew where to go next.
(define ocr-digit-one (list "   "
                            "  |"
                            "  |"))

;; should split and parse first digit
(assert-string= "1" (parse-line ocr-digit-one))
Parsing a line would need to split the line into digits and then parse each digit. I added another two stubbed functions and built parse-line to get the test green.
;; stub
(define (split-digits ocr-line)
    ocr-digit-one)

;; stub
(define (parse-digits ocr-digits)
    ;; use assert-list= to check that ocr-digits is ocr-digit-one
    "1")

(define (parse-line ocr-line)
    (parse-digits (split-digits ocr-line)))
Parenthesis(Actually I was cheating here: I should have checked that the output of split-digits was fed into parse-digits. Nobody is perfect and I will atone for that later, but let's move on for now.) Again a function was finished but I had invented two new collaborating functions to do so.

Another step "outside-in"
Next came testing split-digits to split the three lines into nine digits containing three lines of three characters each.
(define two-ocr-digit-one (list "      "
                                "  |  |"
                                "  |  |"))

;; missing test "should split empty line into no digits"

(assert-list= (list-equals-for string=?)
              "should split single digit"
              (list ocr-digit-one)
              (split-digits ocr-digit-one))

(assert-list= (list-equals-for string=?)
              "should split two digits"
              (list ocr-digit-one ocr-digit-one)
              (split-digits two-ocr-digit-one))

(define (split-digits ocr-line)
    (define (take-3-chars s)
        (substring s 0 3))
    (define (drop-3-chars s)
        (substring s 3 (string-length s)))
    (if (zero? (string-length(car ocr-line)))
        '()
        (append (list (map take-3-chars ocr-line))
                (split-digits (map drop-3-chars ocr-line)))))
I did not start with the degenerate test-case that an empty line, a list of three empty strings, should be split into an empty list of digits. I did not add this test because it did not feel right from the solution's perspective. split-digits would always be called with a full line, i.e. three strings of 27 characters each. But as soon as I tried to get the recursion for the second digit right (as forced by the second test), I struggled because I had to figure out the recursion and termination condition at the same time.

ParenthesisA Functional TDD "Pattern"
There is some obvious pattern here. Consider we need a function that operates on a list of inputs and processing of a single input is either simple or can be delegated to another function. Then we need three tests to drive the implementation of that function:
  1. An empty input should produce an empty output, where empty is defined differently for input and output. This drives the creation of the function header and the body of the (future) termination condition.
  2. A single input should produce a single output. This drives the conditional for the termination condition and the processing of a single input. The processing must be simple otherwise the step is too large.
  3. A list of inputs should produce a list of outputs. This test drives the splitting of the first input from the remaining ones for the recursion.

Coming to an end
The second missing function was parse-digits. It was supposed to work on a list of digits, to parse each of them and return the list of parsed digits so I used my three steps from above.
;; should parse empty digits as empty string
(assert-string= "" (parse-digits (list)))

;; stub
(define (parse-digit ocr-digit)
    "1")

;; test for parsing a single digit omitted

;; should parse digits into numbers for each digit
(assert-string= "111"
                (parse-digits (list ocr-digit-one ocr-digit-one ocr-digit-one)))

(define (parse-digits ocr-digits)
    (if (null? ocr-digits)
        ""
        (string-append (parse-digit (car ocr-digits))
                       (parse-digits (cdr ocr-digits)))))
I skipped step two of my list above and omitted the test for parsing a single digit because I felt confident and delegated the actual parsing of a single digit to another function. parse-digit was the final function and compared a given digit against stored digits to determine the number.
(assert-string= "should parse one"
                (parse-digit ocr-digit-one))

(assert-string= "should parse two"
                (parse-digit ocr-digit-two))

;; etc.

(define (parse-digit ocr-digit)
    (let ((digit (apply string-append ocr-digit)))
        (cond ((string=? digit (string-append "   "
                                              "  |"
                                              "  |")) "1")
        ;; etc.
    )
  )
)
I did not push the final solution of parse-digit. Probably I could remove the duplication using some functional magic, but I had spent some hours already on coding, it was late and I was tired. The full source is available here.

Conclusion
Using Scheme was fun. I had to look up library functions a lot and spent some time on Stackoverflow, but I felt progress all the time. I committed on red, green and refactor, on average each ten minutes, and I was never stuck. The minimalist unit testing function gave me enough feedback to move forward quickly. I did not bother for expressive assertion messages because my steps were small and I never looked at the failures anyway - I knew which test would fail or pass next. Solving Bank OCR was straight forward, probably due to the nature of the assignment. Also knowing the solution - which is not the implementation - helped me a lot and I focused almost entirely on Scheme and the functional aspect.

I was able to do outside-in TDD by stubbing future functions. The stubbing was crude, I just redefined the functions in the test code. I was unhappy with this approach but it worked and I lacked in-depth knowledge of Scheme to come up with a proper way to stub functions. It seemed wrong to pass functions around according to Dependency Inversion Principle, because the called functions were low-level internals and no peer collaborators. In a way I followed Ralf Westphal's approach of True Stepwise Refinement, where he stubbed private functions. In the end I thought about deleting (some) unit tests of the internal functions but did not have any conclusive ideas how to do so.

P.S. for Claus
We had agreed to code together and do some serious product development, and again I spent time on weird ideas like Scheme or Assembly. I am sorry.