Shoddy Documentation

The Stack

The machine under the costume — watched, one word at a time.

The guide's chapter 7 reveals that every Shoddy program compiles onto a stack machine, and the spec's Appendix A states its laws. This page is the part in between: the machine actually running, with the stack drawn after every word. It ends with a full tour of EvenSquareSum. Every example here was run through the mill (Shoddy's command-line tool). You can paste any of them and watch for yourself.

1. One pile of values

The whole machine is a single pile — the stack. A program is a sequence of words, executed left to right, top to bottom. Each word does exactly one kind of thing: take however many values it needs off the top of the pile, and leave its results on the top of the pile. Nothing else. No statements, no expression tree, no operator precedence (rules about which operation goes first). By the time the machine runs, all of that has been compiled away into an order of words.

Every word documents what it eats and what it leaves in a stack-effect signature. Read one left to right, with the top of the stack rightmost:

( Number -- Number )        ' eats one number, leaves one number
( a b -- b a )              ' eats two values, leaves them swapped
( List Quotation -- List )  ' what Filter and Map look like

In every diagram on this page, the stack is drawn the same way: bottom on the left, top on the right. New values arrive on the right; words eat from the right.

2. Warm-up: Square, watched

The guide's two-word squaring function:

Def Square ( Number -- Number )
    Dup *

Call Square(7) — which the mill compiles to 7 Square — and watch the pile:

WordStack afterWhat happened
77a literal pushes itself
Dup7  7copy the top value
*49eat the top two, leave their product

That's the entire trick of the two-word body. * needs two values and we only have one, so Dup manufactures the second. The function's result is simply whatever is on the stack when its body ends.

One size up — the hypotenuse, the longest side of a right-angled triangle. Hypot(3, 4) compiles to 3 4 Hypot:

Def Hypot ( Number Number -- Number )
    Dup * Swap Dup * + Sqr
WordStack afterWhat happened
33
43  4arguments load left to right — last on top
Dup3  4  4
*3  164², done
Swap16  3bring the 3 to the top — its turn
Dup16  3  3
*16  93², done
+25
Sqr5the square root, and the answer

Read the body again — Dup * Swap Dup * + Sqr — and it narrates itself now: square the top, swap, square the other, add, root.

3. The shuffle words

Squaring needed Dup. The hypotenuse needed Swap. They come from a small standard family of shuffle words, each rearranging the top of the pile and nothing more:

WordEffectBefore → after
Dup( a — a a )1 21 2 2
Drop( a — )1 21
Swap( a b — b a )1 22 1
Over( a b — a b a )1 21 2 1
Rot( a b c — b c a )1 2 32 3 1
Nip( a b — b )1 22
Tuck( a b — b a b )1 22 1 2
Depth( — n )1 21 2 2 (pushes the count)

Take is the escape hatch, for when three values need rearranging and the shuffle spelling stops being fun. It pops values into immutable named bindings — names that hold a value and never change. The topmost value goes to the rightmost name. After that you just say the names in whatever order you please:

Def Middle ( a b c -- b )
    Take x, y, z        ' z was on top, x deepest
    y

Take is the exit from Swap Rot Dup shuffle-hell — Appendix A calls it exactly that. Use shuffle words when the dance is one or two steps. Use Take the moment you have to stop and count.

4. Code on the pile: quotations

One more kind of value can sit on the stack: code that hasn't run yet. A value like that is called a quotation — code in a box, saved to run later. Brackets make one: [ Square ] pushes a package containing the word Square, without running it. Words like Map, Filter, Fold, Call, and Ifte eat a quotation and run it, on their own terms. Words that do this are called combinators — ordinary words whose job is to run the code you hand them. Map runs it once per item, Ifte runs one of two, Call just runs it.

And here is the neatest fact in the whole core: a list is a quotation of literals. The surface's { 1, 2, 3 } compiles to exactly [ 1 2 3 ] — a package of code that, run, would push 1, 2, 3. A list and a quotation are the same thing: run a list and it pushes its items. (The Joy language calls this property homoiconicity; Shoddy inherits it whole.) That's why this page draws lists and quotations the same way in the traces below. They are the same kind of thing on the pile.

5. EvenSquareSum: the full tour

Now the showpiece from the guide and the spec — sum of the squares of the even numbers up to N:

Def EvenSquareSum(n As Number) As Number
    Range(1, n)
        Filter(IsEven)
        Map(Square)
        Fold(0, +)

What does the mill make of this? Three desugarings, all mechanical. (A desugaring is a rewrite from the friendly spelling into the plain one.) The parameter becomes a Take. Each call f(a, b) becomes a b f. And each bare function name in argument position — IsEven, Square, + — becomes a quotation. The indented lines are pipeline chaining: each call's missing first argument is simply whatever the previous line left on the stack. Flattened, the body is:

Take n
1 n Range  [ IsEven ] Filter  [ Square ] Map  0 [ + ] Fold

(Run it yourself — that spelling compiles and returns the same answers as the sugared one.) Here is the whole execution at n = 4, stack drawn after every word:

WordStack afterWhat happened
Take nthe argument 4 leaves the pile, named n
11
n1  4a binding pushes its value
Range[ 1 2 3 4 ]eats both bounds, leaves one list
[ IsEven ][ 1 2 3 4 ]  [ IsEven ]a quotation — pushed, not run
Filter[ 2 4 ]eats list and test, runs the test per item
[ Square ][ 2 4 ]  [ Square ]
Map[ 4 16 ]runs Square on each item
0[ 4 16 ]  0the fold's starting accumulator
[ + ][ 4 16 ]  0  [ + ]
Fold20eats all three, leaves the sum

Notice the shape of the whole thing. The pile never gets more than three deep, and between stages it holds exactly one value — the data, flowing left to right. That's why the surface version reads as a pipeline: each line's result is just sitting there, and the next line's word consumes it as its first argument. The stack isn't an implementation detail the pipeline hides. The stack is why the pipeline works.

Zoom: inside the Fold

Fold looks like one step in the trace, but it runs its quotation once per item. Each result feeds back in as the next accumulator — the running total so far. On Fold's own private beat:

IterationRunsAccumulator after
start0
item 40  4  +4
item 164  16  +20

Each iteration is itself just the machine again: push the accumulator, push the item, run the quotation — which here is one word, +. Combinators aren't magic. They're loops that push things and run your quotation.

6. Watch it live

You don't have to trust this page's tables — the toolchain will draw the pile for you, two ways:

7. Writing the dialect yourself

Leave the parentheses off a Def header and the body is stack code — that's the whole switch:

Def Square ( Number -- Number )     ' concatenative: effect signature
    Dup *

Def Square(n As Number) As Number   ' applicative: same word, other costume
    n * n

The two dialects mix on any line, in both directions. Each has its home ground. Stack style (the technical name is point-free) wins when data genuinely flows in a line — pipelines, wrappers, one-liners like Dup * where a variable would be ceremony. Named-argument style (applicative) wins the moment logic branches or three things need juggling. Two honest gotchas come from mixing them, and both will bite you exactly once:

The law lives in Appendix A — one page, worth reading now that you've watched the machine it governs.

8. The same stack, held as a value

Everything on this page happens at weave time, while the program is being compiled. The pile you've been watching is compiled away, and a running Shoddy program never holds it as a value. cuttle lets a running program hold a stack of its own as a value. It is that same model reified — made into a real thing the program can touch: a heterogeneous Stack (one that holds values of any type) that a mill can push onto, pop and inspect at runtime. The shuffle words above (Dup, Swap, Rot, and the rest) become an ordinary pure algebra over it — plain functions from stack to stack — rather than compiler machinery. reckoner lets you type stack code as a line of text and watch it run. It goes one step further and puts an evaluator in front of that stack, so that typed RPN lines like 3 4 + run against a dictionary — the set of words the evaluator knows — the way this page's traces run against the compiled stack. (RPN is Reverse Polish Notation: exactly this page's order, values first, then the word.) Thirteen seed files bridge the rest of the standard library into that dictionary. reckoner's own page lists them.

If you would rather watch it than read it, the halifax calculator's TRACE mode prints these same traces live — one row per token, for whatever you type at its prompt. It indents each row by how deep the sub-evaluation is, because a program running on a fresh stack is a different pile from the one it was called on.