Skip to content
LAM
Read Home Blog
Make Projects HTML Tools Games
Touch grass Notes Resume Links
Home Blog HTML Projects
Tools Games Notes Resume Links
Back SICP: The Wizard Book - 8-Bit Edition Programming
Download Open
Show description 2,145 chars · Programming

SICP: The Wizard Book - 8-Bit Edition

SICP: The Wizard Book - 8-Bit Edition








SICP: The Wizard Book

A High-Level Grimoire of Computational Spells





Level 1
Level 2
Level 3
Level 4
Level 5





Level 1: Building Abstractions with Procedures

This is where the journey begins. We learn that programming isn't just telling a machine what to do; it's about creating powerful abstractions. Here, the primary tool of abstraction is the procedure.


Key Scrolls & Incantations:


Elements of Programming: The core trinity of primitive expressions (the simplest entities), means of combination (building complex things from simple ones), and means of abstraction (naming complex things to treat them as single units).

Prefix Notation: Lisp's style of writing expressions, where the operator comes first: (+ 2 3). It's clean, unambiguous, and allows for a variable number of arguments.

Procedures as Abstractions: We use define to give names to compound operations. This allows us to think at a higher level, hiding the underlying details.
; Defining the concept of squaring
(define (square x) (* x x))


Evaluation Models: The substitution model is our first mental model for how procedures work: replace parameters with arguments and evaluate. Simple, but powerful.

Processes & Orders of Growth: We analyze how procedures consume resources (time and space).

Linear Recursion vs. Iteration: A recursive process expands and then contracts (e.g., recursive factorial), using growing space. An iterative process maintains a fixed number of state variables (e.g., iterative factorial), using constant space. Tail recursion is the magic that lets us write iterative processes with recursive syntax.

Tree Recursion: Processes that branch, like the naive Fibonacci procedure. Often elegant but can be inefficient due to re-computation.

Order of Growth (Big-Theta Θ): A way to classify how resource usage scales. We encounter Θ(n) (linear), Θ(log n) (logarithmic), and Θ(2n) (exponential) processes.




Higher-Order Procedures: The true source of power. Procedures can accept other procedures as arguments and return procedures as values.…

SICP: The Wizard Book - 8-Bit Edition

18,279 bytes · HTML source
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>SICP: The Wizard Book - 8-Bit Edition</title>
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&family=VT323&display=swap" rel="stylesheet">
    <style>
        :root {
            --background-color: #0d0d0d;
            --primary-text: #00ff00;
            --header-text: #00ffff;
            --secondary-text: #f0f0f0;
            --accent-color: #ff00ff;
            --border-color: #00ff00;
            --code-bg: #1a1a1a;
        }

        body {
            background-color: var(--background-color);
            color: var(--secondary-text);
            font-family: 'VT323', monospace;
            font-size: 20px;
            line-height: 1.6;
            margin: 0;
            padding: 20px;
        }

        .container {
            max-width: 900px;
            margin: 0 auto;
            border: 4px solid var(--border-color);
            padding: 20px;
            box-shadow: 0 0 15px var(--border-color);
        }

        header {
            text-align: center;
            margin-bottom: 40px;
        }

        h1 {
            font-family: 'Press Start 2P', cursive;
            color: var(--header-text);
            font-size: 2.5rem;
            text-shadow: 3px 3px 0px var(--accent-color);
            animation: flicker 1.5s infinite alternate;
        }

        h1::after {
            content: '_';
            animation: blink 1s step-end infinite;
        }

        @keyframes blink {
            50% { opacity: 0; }
        }

        @keyframes flicker {
            0%, 18%, 22%, 25%, 53%, 57%, 100% {
                text-shadow:
                0 0 4px #fff,
                0 0 11px #fff,
                0 0 19px #fff,
                0 0 40px var(--header-text),
                0 0 80px var(--header-text),
                0 0 90px var(--header-text),
                0 0 100px var(--header-text),
                0 0 150px var(--header-text);
            }
            20%, 24%, 55% {        
                text-shadow: none;
            }
        }

        h2, h3 {
            font-family: 'Press Start 2P', cursive;
            color: var(--primary-text);
            border-bottom: 3px solid var(--accent-color);
            padding-bottom: 10px;
            margin-top: 40px;
        }
        
        h2 {
            font-size: 1.8rem;
        }

        h3 {
            font-size: 1.2rem;
            color: var(--header-text);
            border-bottom-style: dashed;
        }

        .level-select {
            display: flex;
            flex-wrap: wrap;
            justify-content: center;
            gap: 10px;
            margin-bottom: 40px;
        }

        .level-select a {
            font-family: 'Press Start 2P', cursive;
            text-decoration: none;
            color: var(--background-color);
            background-color: var(--primary-text);
            padding: 10px 15px;
            border: 2px solid var(--border-color);
            transition: all 0.2s ease-in-out;
        }

        .level-select a:hover {
            background-color: var(--accent-color);
            color: var(--secondary-text);
            box-shadow: 0 0 10px var(--accent-color);
            transform: translateY(-3px);
        }

        section {
            margin-bottom: 40px;
        }

        p {
            margin-bottom: 15px;
        }

        code {
            font-family: 'VT323', monospace;
            background-color: var(--code-bg);
            color: var(--accent-color);
            padding: 2px 5px;
        }
        
        pre {
            background-color: var(--code-bg);
            border: 2px solid var(--border-color);
            padding: 15px;
            overflow-x: auto;
            color: var(--secondary-text);
        }

        pre code {
            background-color: transparent;
            padding: 0;
            color: inherit;
        }
        
        pre .keyword {
            color: var(--header-text);
            font-weight: bold;
        }
        
        pre .comment {
            color: #888;
        }
        
        pre .symbol {
            color: var(--primary-text);
        }

        ul {
            list-style: none;
            padding-left: 0;
        }

        ul li {
            position: relative;
            padding-left: 25px;
            margin-bottom: 10px;
        }

        ul li::before {
            content: '>';
            position: absolute;
            left: 0;
            color: var(--primary-text);
        }

        strong {
            color: var(--primary-text);
            font-weight: normal;
        }
    </style>
</head>
<body>
    <div class="container">
        <header>
            <h1>SICP: The Wizard Book</h1>
            <p>A High-Level Grimoire of Computational Spells</p>
        </header>

        <nav class="level-select" aria-label="Level Select">
            <a href="#level-1">Level 1</a>
            <a href="#level-2">Level 2</a>
            <a href="#level-3">Level 3</a>
            <a href="#level-4">Level 4</a>
            <a href="#level-5">Level 5</a>
        </nav>

        <main>
            <!-- LEVEL 1 -->
            <section id="level-1">
                <h2>Level 1: Building Abstractions with Procedures</h2>
                <p>This is where the journey begins. We learn that programming isn't just telling a machine what to do; it's about creating powerful abstractions. Here, the primary tool of abstraction is the <strong>procedure</strong>.</p>
                
                <h3>Key Scrolls & Incantations:</h3>
                <ul>
                    <li><strong>Elements of Programming:</strong> The core trinity of <strong>primitive expressions</strong> (the simplest entities), <strong>means of combination</strong> (building complex things from simple ones), and <strong>means of abstraction</strong> (naming complex things to treat them as single units).</li>
                    <li><strong>Prefix Notation:</strong> Lisp's style of writing expressions, where the operator comes first: <code>(+ 2 3)</code>. It's clean, unambiguous, and allows for a variable number of arguments.</li>
                    <li><strong>Procedures as Abstractions:</strong> We use <code>define</code> to give names to compound operations. This allows us to think at a higher level, hiding the underlying details.
<pre><code class="language-scheme"><span class="comment">; Defining the concept of squaring</span>
(<span class="keyword">define</span> (<span class="symbol">square</span> x) (* x x))</code></pre>
                    </li>
                    <li><strong>Evaluation Models:</strong> The <strong>substitution model</strong> is our first mental model for how procedures work: replace parameters with arguments and evaluate. Simple, but powerful.</li>
                    <li><strong>Processes & Orders of Growth:</strong> We analyze how procedures consume resources (time and space).
                        <ul>
                            <li><strong>Linear Recursion vs. Iteration:</strong> A recursive process expands and then contracts (e.g., recursive factorial), using growing space. An iterative process maintains a fixed number of state variables (e.g., iterative factorial), using constant space. <strong>Tail recursion</strong> is the magic that lets us write iterative processes with recursive syntax.</li>
                            <li><strong>Tree Recursion:</strong> Processes that branch, like the naive Fibonacci procedure. Often elegant but can be inefficient due to re-computation.</li>
                            <li><strong>Order of Growth (Big-Theta &Theta;):</strong> A way to classify how resource usage scales. We encounter &Theta;(n) (linear), &Theta;(log n) (logarithmic), and &Theta;(2<sup>n</sup>) (exponential) processes.</li>
                        </ul>
                    </li>
                    <li><strong>Higher-Order Procedures:</strong> The true source of power. Procedures can accept other procedures as arguments and return procedures as values. This lets us abstract common computational patterns.
<pre><code class="language-scheme"><span class="comment">; A general summation procedure</span>
(<span class="keyword">define</span> (<span class="symbol">sum</span> term a next b)
  (<span class="keyword">if</span> (> a b)
      0
      (+ (term a)
         (<span class="symbol">sum</span> term (next a) next b))))</code></pre>
                    </li>
                </ul>
            </section>

            <!-- LEVEL 2 -->
            <section id="level-2">
                <h2>Level 2: Building Abstractions with Data</h2>
                <p>Procedures are not enough. We need to model complex objects. This level introduces <strong>data abstraction</strong>, a methodology for separating how we *use* data from how it is *represented*.</p>
                
                <h3>Key Scrolls & Incantations:</h3>
                <ul>
                    <li><strong>Data Abstraction Barriers:</strong> The core idea. We create a "wall" between our program and the data representation. The wall is made of <strong>constructors</strong> (which build the data) and <strong>selectors</strong> (which get pieces of it). If we change the representation, we only need to change the constructors/selectors, not the rest of the program.</li>
                    <li><strong>Pairs - The Universal Glue:</strong> The primitive pair, built with <code>cons</code> and accessed with <code>car</code> and <code>cdr</code>, is all we need to build any data structure imaginable.</li>
                    <li><strong>Closure Property:</strong> The result of combining data objects (e.g., with <code>cons</code>) can itself be combined using the same operation. This is what allows us to build hierarchical structures like lists and trees.</li>
                    <li><strong>Sequences as Conventional Interfaces:</strong> Using lists as a standard representation for sequences allows us to create a powerful toolkit of higher-order procedures like <code>map</code>, <code>filter</code>, and <code>accumulate</code> that can be mixed and matched to build complex programs elegantly.
<pre><code class="language-scheme"><span class="comment">; Sum of the squares of odd numbers in a sequence</span>
(<span class="symbol">accumulate</span> +
              0
              (<span class="symbol">map</span> square
                   (<span class="symbol">filter</span> odd? sequence)))</code></pre>
                    </li>
                    <li><strong>Symbolic Data & Quotation:</strong> We introduce the ability to manipulate symbols, not just numbers. The <code>quote</code> special form (or <code>'</code>) prevents Lisp from evaluating an expression, treating it as literal data.</li>
                    <li><strong>Multiple Representations & Tagged Data:</strong> The same abstract data (e.g., a complex number) can be represented in multiple ways (e.g., rectangular vs. polar coordinates). We use <strong>type tags</strong> to label data so that our procedures can operate on different representations generically.</li>
                </ul>
            </section>
            
            <!-- LEVEL 3 -->
            <section id="level-3">
                <h2>Level 3: Modularity, Objects, and State</h2>
                <p>The world changes. Until now, our programs have been timeless. Here we introduce <strong>state</strong> and <strong>time</strong> into our computational models using the assignment operator <code>set!</code>. This is a dangerous but powerful magic.</p>
                
                <h3>Key Scrolls & Incantations:</h3>
                <ul>
                    <li><strong>Assignment and Local State:</strong> <code>set!</code> allows us to change the value associated with a variable. By encapsulating state variables within procedures, we can create computational objects that model real-world objects with changing state (e.g., a bank account).</li>
                    <li><strong>The Cost of Assignment:</strong> Introducing <code>set!</code> destroys <strong>referential transparency</strong>. The same expression can yield different values at different times. This makes reasoning about programs much harder and invalidates our simple substitution model of evaluation.</li>
                    <li><strong>The Environment Model of Evaluation:</strong> A more robust model for understanding procedure application. A procedure is a pairing of code and a pointer to its definition environment. When a procedure is called, a new frame is created where parameters are bound to arguments, and this frame's enclosing environment is the procedure's definition environment. This model correctly explains how local state works.</li>
                    <li><strong>Modeling with Mutable Data:</strong> We can now build classic data structures that rely on modification, such as queues and tables. Sharing and identity become critical concepts.</li>
                    <li><strong>Concurrency:</strong> The real world has things happening at the same time. Modeling this introduces new problems: multiple processes trying to access and modify shared state simultaneously can lead to chaos. This requires <strong>synchronization mechanisms</strong>, such as serializers and mutexes, to control concurrency and prevent bugs like race conditions and deadlock.</li>
                </ul>
            </section>

            <!-- LEVEL 4 -->
            <section id="level-4">
                <h2>Level 4: Metalinguistic Abstraction</h2>
                <p>The ultimate power: we stop being mere users of a language and become designers. We learn that <strong>an evaluator for a language is just another program</strong>. We build our own Lisp interpreter... in Lisp.</p>
                
                <h3>Key Scrolls & Incantations:</h3>
                <ul>
                    <li><strong>The Metacircular Evaluator:</strong> The core of the interpreter is a cycle between two procedures: <code>eval</code> and <code>apply</code>.
                        <ul>
                            <li><code>eval</code> takes an expression and an environment and determines how to evaluate it (e.g., is it a number, a variable, an <code>if</code>, a procedure application?).</li>
                            <li><code>apply</code> takes a procedure and a list of arguments and applies the procedure to the arguments. For compound procedures, this involves evaluating the procedure's body in a new environment.</li>
                        </ul>
                    </li>
                    <li><strong>Data as Programs:</strong> The evaluator bridges the gap between programs as instructions and programs as data. An expression like <code>(+ 1 2)</code> is, from the evaluator's perspective, just a list of three symbols.</li>
                    <li><strong>Language Variations:</strong> Since our evaluator is a program we can change, we can change the language!
                        <ul>
                            <li><strong>Lazy Evaluation:</strong> We modify the evaluator to delay the evaluation of arguments until their values are actually needed. This allows us to work with "infinite" data structures seamlessly.</li>
                            <li><strong>Nondeterministic Computing:</strong> We introduce the <code>amb</code> operator, which allows an expression to have multiple possible values. The evaluator automates the search and backtracking needed to find a value that satisfies a set of constraints. This is a powerful tool for solving logic puzzles and parsing problems.</li>
                        </ul>
                    </li>
                </ul>
            </section>

            <!-- LEVEL 5 -->
            <section id="level-5">
                <h2>Level 5: Computing with Register Machines</h2>
                <p>We descend from the high-level abstractions of Lisp to the bare metal. We model computation in terms of a simple, traditional computer—a <strong>register machine</strong>. This reveals the final layer of magic: how control is implemented.</p>

                <h3>Key Scrolls & Incantations:</h3>
                <ul>
                    <li><strong>Register Machine Design:</strong> A machine is defined by its <strong>data paths</strong> (registers and primitive operations) and a <strong>controller</strong> (a sequence of instructions that orchestrate the operations).</li>
                    <li><strong>The Stack for Recursion:</strong> Recursive procedures require a mechanism for suspending a computation, solving a subproblem, and then resuming. A <strong>stack</strong> (a Last-In, First-Out data structure) is the mechanism used to save and restore register contents (like the return address) across nested procedure calls.</li>
                    <li><strong>The Explicit-Control Evaluator:</strong> We rewrite our Lisp interpreter as a single, large register machine. This makes the flow of control, argument passing, and stack management completely explicit. It finally explains how tail recursion works: a tail-recursive call is simply a <code>goto</code>, which doesn't consume stack space.</li>
                    <li><strong>Compilation:</strong> The final step. A <strong>compiler</strong> translates a source program (in Scheme) into an equivalent program in the lower-level language of a target machine (our register machine). Instead of interpreting the source expression tree at runtime, the machine directly executes a pre-analyzed sequence of instructions, yielding a significant speedup.</li>
                </ul>
            </section>
        </main>
    </div>
</body>
</html>