Thinking Forth Reading Notes

I recently looked into Thinking Forth by Leo Brodie to learn about the origins of test-driven development and similar project management methods that are popular today. This post contains the solutions to the exercises which in some aspects differs from the implementation discussed in the book. Most of the concepts are still working and understandable, though.

I have also looked into Starting Forth by the same author and did the exercises in Gforth, see this article.

Chapter 1

This chapter talks about components and different “levels” of programming languages. As Forth is an extensible language, it can serve both as a low-level as well as a high-level language.

Today, the power of any software system depends less on what you can in principle do with it, but how fast you can practically get something done with it. Questions like how can I apply a regular expression to a UTF-8 string or get the subject lines of all new messages on the IMAP-server. Thus, the lessons on the extensibility of Forth are not relevant in todays’ world.

On the other hand, the discussion of modularization is very good and highly readable and still relevant today for designing a new system.

Chapter 2

This chapter is indeed a great introduction to the basics of many agile development methodologies in use today. Iterative design and gradual addition of complexity are distinct from the classical waterfall model.

The highlight of this chapter is a thorough discussion of how to implement a table of rules for telephone charges. It is fascinating to see how the design choices lead to different complexities and how a “minimalistic” approach leads to different results than the most general one.

Chapter 3

A detailed discussion of the design process. The example – a text editor – is quite dated, of course. What I am missing here entirely is a discussion of how the interface is perceived by the user. This latter point would be very important today, whereas the actual implementation of the interface has become less relevant.

My answer to “Further Thinking” question 1:

Q1
I prefer answer (a) because the actual keystroke interpreter is more system-independent and easier to read. Note, that the suggested answer in the book is different from mine, because I believe that such basic components of the UI (like key codes) never belong in a user-level program.

Chapter 4

This chapter is an excellent treatise on problem-solving techniques! I think it is very well written and is useful far beyond teaching about programming or software design. Tip 4.24 is an important insight even today. Again, the roman numerals are an interesting practical application of the workflow discussed in this chapter; they need some minor adaption for Gforth, however. Here it is:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
create romans      ( ones) char I c, char V c,
                   ( tens) char X c, char L c,
               ( hundreds) char C c, char D c,
              ( thousands) char M c,

variable column# ( current_offset)
: ones 0 column# ! ;
: tens 2 column# ! ;
: hundreds 4 column# ! ;
: thousands 6 column# ! ;

: column ( -- address-of-column) romans column# @ + ;

: .symbol ( offset -- ) column + c@ emit ;
: oner 0 .symbol ;
: fiver 1 .symbol ;
: tener 2 .symbol ;

: oners ( #-of-oners -- )
    ?dup if 0 do oner loop then ;
: almost ( quotient-of-5/ -- )
    oner if tener else fiver then ;
: digit ( digit -- )
    5 /mod over 4 =
    if almost drop
    else
	if fiver then
	oners then ;

: roman ( number -- )
    1000 /mod thousands digit
    100 /mod hundreds digit
    10 /mod tens digit
    ones digit ;

My answer to “Further Thinking” is an implementation of the Durstenfeld shuffle:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
\ Main user interface words:
\ init-card-storage -- Sets up a standard sorted card order.
\ type-cards        -- Types the entire deck to stdout.
\ shuffle-cards     -- Shuffles (unbiased) the array of cards.

s" random.fs" included

\ Representation of cards (standard 52-card Poker deck).
52 constant #cards
0  constant Clubs
1  constant Hearts
2  constant Spades
3  constant Diamonds
0  constant Ace
10 constant Jack
11 constant Queen
12 constant King

create card-storage #cards cells allot

\ Create and initialize storage of card array.
: init-cards ( -- )
    #cards 0 u+do
        i card-storage i cells + !
    loop ;

	
\ User-interface for handling the array of cards.
: suite-type ( n -- )
    case
        Clubs    of ." Clubs"    endof
        Hearts   of ." Hearts"   endof
        Spades   of ." Spades"   endof
        Diamonds of ." Diamonds" endof
    endcase ;

: value-type ( n -- )
    dup Ace   = if ." Ace "   else
    dup Jack  = if ." Jack "  else
    dup Queen = if ." Queen " else
    dup King  = if ." King "  else
    dup 1+ . then then then then drop ;

\ Type a single card with given number to stdout.
: card-type ( n -- )
    assert( dup 0 >= )
    assert( dup #cards < )
    13 /mod swap value-type ." of "
    suite-type ." ." cr ;

\ Type the current card-array to stdout.
: cards-type ( -- )
    #cards 0 u+do
	card-storage i cells + @ card-type
    loop ;

\ Shuffle the cards using the Durstenfeld shuffle.
\ @note The cards need to be initialized first.

\ Exchange cards at positions n1 and n2.
: exchange-cards ( n1 n2 -- )
    { n1 n2 }
    card-storage n1 cells + @
    card-storage n2 cells + @
    card-storage n1 cells + !
    card-storage n2 cells + ! ;

: shuffle-cards ( -- )
    #cards begin
        dup while
	    dup random swap 1- tuck exchange-cards
    repeat ;

Chapter 5

This chapter is very specific to the Forth language. Especially the remarks on program structure within blocks are dated as nowadays programs are much more complex than they used to be back then.

The three (language-independent) takeaways for me from this chapter are: (1) Every project should have coding conventions; these need not be overly complex, but will depend on the size of the group and how people work together. (2) Individual procedures should not be longer than a single screen. I have heard this in the context of Objective-C in the form that each method should fit on the screen (which will, of course, depend on everybody’s individual definition of screen size). (3) The choice of names in the program is art, and an important art to master!

Chapter 6

This chapter has sound advice on refactoring techniques and iterative design. Apart from the Forth-specific syntax and strategies, the general ideas are applicable to contemporary design, not only of software.

Regarding the specific software factoring techniques, however, I am missing everything related to object oriented development. I found that the advice on not defining elementary things like array unless you actually need them feels a little comical today. When compared to a contemporary language like Python, the lack of essential functionality is a huge problem.

One could argue, though, that today’s programs are much more powerful than back then and what is considered sine-qua-non today may not have been crucial 30 years ago.

Chapter 7

The discussion of stack effects is very Forth-specific and with the contemporary technique of local variables no longer necessary. The subsequent discussion of variables and states and of DOER/MAKE is an interesting read, but generally speaking I did not learn much in this chapter.

Chapter 8

This chapter is highly practical and full of useful advice on refactoring control structures. This is absolutely worthwhile to read!

Some “problems” being discussed are Forth-specific, though. For example, the and and or conditional macros of Lisp are smart enough not to evaluate all of their arguments as soon as the outcome is clear. Forth does not offer similar conditionals and one needs to resort to explicit control structures for the same effect.

The idea of using boolean numbers as values and vice-versa is pretty cool.

On the downside I am wary of the advice that I shouldn’t be checking for errors that can’t possibly happen. In my experience the definition of something that “can’t possibly happen” depends on the imagination (or lack thereof) of the programmer rather than anything that may happen in an actual program. Last but not least, playing around with the return stack certainly is not a practical or recommendable technique these days. This advice absolutely needs to be ignored!

Summary and Conclusions

I found a few pieces of outdated advice, but also some hidden gems in this book. Some of the practices I disagree with, but I have to admit that my disagreement is sometimes based on different assumptions I made. The true value of different perspectives in this book is that they challenge me to question and justify my way of doing things.

And this opportunity for reflection was definitely worthwhile the time to read it!

Which books on older programming languages have you read recently? What is the most important lesson you learned from them?