Lesson 1 ======== - Definitions and procedure calls - Formal parameters and actual arguments - `if` and `cond` - Simple recursion and guards - Pairs, `car`, `cdr`, and proper lists - Executable checks Move from reading expressions to writing small procedures whose behavior can be checked deliberately. ## Goal After this lesson you should be able to read a small R7RS file, define constants and procedures, compose procedure calls, use `if` and `cond`, recognize a simple recursive procedure, read basic pair/list operations, and write checks that confirm what your code does. The examples are still small on purpose. The point is to establish habits that scale: name useful ideas, compose procedures, keep inputs and outputs clear, and test what you claim. ## R7RS Program Shape The browser examples on this page are convenient for experiments, but the checked source file is an R7RS program. A small R7RS program begins with imports: ```scheme (import (scheme base) (scheme write) (scheme process-context)) ``` `(scheme base)` supplies the core language bindings used here: procedure calls, definitions, predicates, arithmetic, conditionals, pairs, lists, recursion, and common list procedures. `(scheme write)` gives output procedures such as `display` and `write`. `(scheme process-context)` gives `exit`, which the test program uses to report success or failure to the shell. ## Functional Programming, in the Small Scheme can be used in several styles. This course begins with functional programming: write procedures that compute values from arguments, then combine those procedures into larger expressions. For the early lessons, a procedure should usually behave like a mathematical function: the same arguments produce the same result. That makes examples easier to test and easier to reason about. ```scheme (define (square-number n) (* n n)) (square-number 5) ``` ```scheme (define (square-number n) (* n n)) (square-number 5) ``` A procedure is not useful only for one particular input. The point of the parameter `n` is that `square-number` works for many numbers. ```scheme (define (square-number n) (* n n)) (square-number (+ 2 3)) ``` The call `(square-number (+ 2 3))` composes two computations. The argument expression `(+ 2 3)` produces `5`; then `square-number` uses that value. ## Formal Parameters and Actual Arguments A procedure definition introduces formal parameters. These are the names used inside the procedure body. ```scheme (define (rectangle-area width height) (* width height)) ``` In the call below, `7` and `6` are actual argument expressions. Their values become the values of `width` and `height` while the body runs. ```scheme (rectangle-area 7 6) ``` ```scheme (define (rectangle-area width height) (* width height)) (rectangle-area 7 6) ``` Keep the distinction clear: - The formal parameter names come from the definition. - The actual argument expressions come from the call. - The actual argument values are the values produced by those expressions. This matters when arguments are themselves compound expressions: ```scheme (define (rectangle-area width height) (* width height)) (rectangle-area (+ 3 4) (- 10 4)) ``` ## Constants and Small Formulas A definition can name a value as well as a procedure. ```scheme (define unit-square-side 1) (define default-width 7) ``` Use names when they make the expression easier to read or when the same value has a role in several places. Small formula procedures are good early practice: ```scheme (define (rectangle-perimeter width height) (* 2 (+ width height))) (define (triangle-area base height) (/ (* base height) 2)) ``` ```scheme (define (rectangle-perimeter width height) (* 2 (+ width height))) (define (triangle-area base height) (/ (* base height) 2)) (rectangle-perimeter 7 6) (triangle-area 8 3) ``` Notice that the browser reports the value of the last expression in the block. The perimeter expression is evaluated, then its value is not shown because the final expression is the triangle-area call. ## `if` Is a Special Form `if` chooses between two expressions: ```scheme (if (> 7 3) 'larger 'smaller) ``` The result is the symbol `larger`. `if` is not an ordinary procedure call. It evaluates the test first, then evaluates only the selected branch. That matters: ```scheme (define (safe-reciprocal n) (if (= n 0) 'undefined (/ 1 n))) ``` ```scheme (define (safe-reciprocal n) (if (= n 0) 'undefined (/ 1 n))) (safe-reciprocal 0) (safe-reciprocal 4) ``` If both branches were evaluated every time, `(safe-reciprocal 0)` would still try to divide by zero. It does not, because `if` has its own evaluation rule. ## `cond` for Several Cases For more than two cases, `cond` is usually clearer than nested `if` forms. ```scheme (define (number-kind n) (cond ((< n 0) 'negative) ((= n 0) 'zero) ((even? n) 'positive-even) (else 'positive-odd))) ``` ```scheme (define (number-kind n) (cond ((< n 0) 'negative) ((= n 0) 'zero) ((even? n) 'positive-even) (else 'positive-odd))) (number-kind -3) (number-kind 0) (number-kind 8) (number-kind 9) ``` Each `cond` clause has a test and a result. The outer form starts with `cond`, so the clause parentheses are not procedure calls. This is one of the early exceptions to the simple rule "parentheses mean a call." More precisely: most parenthesized expressions are calls, but special forms have their own syntax. A small game-like predicate can use `cond` too: ```scheme (define (buzz-basic n) (cond ((= (remainder n 7) 0) 'buzz) ((= (remainder n 10) 7) 'buzz) (else n))) ``` This version says `buzz` for multiples of seven and for numbers whose last decimal digit is seven. A later exercise can generalize it to search all digits. ```scheme (define (buzz-basic n) (cond ((= (remainder n 7) 0) 'buzz) ((= (remainder n 10) 7) 'buzz) (else n))) (buzz-basic 14) (buzz-basic 27) (buzz-basic 31) ``` ## A First Look at Recursion Recursion means a procedure calls itself. Lesson 0 only named the idea. Here we can read two small recursive procedures, but the full method for designing recursive programs comes later. The first example is factorial. A naive definition can be dangerous: if the only base case is `(= n 0)`, a negative input keeps moving farther from `0` and recurses forever. Guard invalid inputs before the recursive case. ```scheme (define (factorial n) (cond ((or (not (integer? n)) (< n 0)) (error "factorial: expected a nonnegative integer" n)) ((<= n 1) 1) (else (* n (factorial (- n 1)))))) ``` There are three parts to notice: - The guard: non-integers and negative integers are rejected before recursion starts. - The base case: when `n` is `0` or `1`, the answer is `1`. - The recursive case: otherwise, multiply `n` by the factorial of the smaller number `(- n 1)`. Using `(<= n 1)` handles both ordinary base cases directly. It also avoids one unnecessary recursive call for `(factorial 1)`, which is small here but is the right habit: do not make examples slower or less safe than they need to be. ```scheme (define (factorial n) (cond ((or (not (integer? n)) (< n 0)) (error "factorial: expected a nonnegative integer" n)) ((<= n 1) 1) (else (* n (factorial (- n 1)))))) (factorial 5) ``` Euclid's algorithm for greatest common divisor has the same shape: a base case and a smaller recursive problem. ```scheme (define (euclid-gcd a b) (if (= b 0) a (euclid-gcd b (remainder a b)))) ``` ```scheme (define (euclid-gcd a b) (if (= b 0) a (euclid-gcd b (remainder a b)))) (euclid-gcd 84 30) ``` Do not try to learn all recursion from these examples at once. For now, read the shape: base case first, recursive call on a simpler input second. ## A Note on Evaluation Order Scheme evaluates procedure arguments before calling the procedure. This is why `(square-number (+ 2 3))` computes the argument value `5` and then calls `square-number` with `5`. There are deeper questions about evaluation order, delayed evaluation, and what happens when an expression is not a pure function of its arguments. Those belong in a later evaluation-model lesson. For now, the practical rule is enough: write small procedures whose results depend on their arguments, and use tests to check that they do. ## A First Look at Pairs and Proper Lists Lesson 0 introduced quoted lists as data: ```scheme '(scheme r7rs lists) ``` A non-empty Scheme list is built from pairs. A pair is a two-part value. The procedure `cons` constructs a pair, `car` selects its first part, and `cdr` selects its second part. ```scheme (define pair-example (cons 'left 'right)) (pair? pair-example) (car pair-example) (cdr pair-example) ``` The names `car` and `cdr` are historical. For now, read them as the two basic selectors for a pair: first part and second part. A proper list is a chain of pairs whose final `cdr` is the empty list, written `'()`. ```scheme (define words (cons 'scheme (cons 'r7rs (cons 'lists '())))) words (car words) (cdr words) (list? words) ``` The same proper list can be written directly with quote: ```scheme (define words '(scheme r7rs lists)) (car words) (cdr words) (null? '()) ``` For a non-empty proper list, `car` gives the first element and `cdr` gives the rest of the list. The rest of the list is itself a list. `null?` recognizes the empty list. The procedure `list` is a convenience constructor for proper lists. It evaluates its arguments, then returns a proper list containing their values. ```scheme (list 'scheme 'r7rs 'lists) (list 1 (+ 1 2) 'scheme) ``` This is different from a quoted list. In `'(1 (+ 1 2) scheme)`, the `(+ 1 2)` is data and is not evaluated. In `(list 1 (+ 1 2) 'scheme)`, the addition is evaluated before `list` receives its arguments. ```scheme '(1 (+ 1 2) scheme) (list 1 (+ 1 2) 'scheme) ``` An improper list is a pair chain that does not end in `'()`. One direct example is: ```scheme (cons 'left 'right) ``` Many Scheme systems print that value as `(left . right)`. It is a pair, but not a proper list. ```scheme (define dotted (cons 'left 'right)) (pair? dotted) (list? dotted) ``` Improper lists are not mistakes by definition. They are pairs being used outside the proper-list convention. Pairs can also represent small custom linked structures: ```scheme (define point (cons 3 4)) (car point) (cdr point) ``` Here the pair means "x coordinate and y coordinate," not "first element and rest of a proper list." Later lessons will elaborate pairs, proper lists, improper lists, association lists, records, and when custom pair-linked structures are a good idea. ## A Minimal Test Habit A lesson example should not only show definitions. It should also check them. Here is the tiny checking pattern used in the current example file: ```scheme (define failures '()) (define (record-failure label expected actual) (set! failures (cons (list label expected actual) failures))) (define (check-equal label expected actual) (unless (equal? expected actual) (record-failure label expected actual))) ``` This is not a full testing library. It is just enough to make examples executable. When a check fails, the label, expected value, and actual value are recorded. A check looks like this: ```scheme (check-equal 'procedure-call 81 (square-number 9)) ``` The quoted symbol `procedure-call` names the check. It is data, not a variable lookup. ## First Complete Example The published example source for this lesson checks arithmetic, composition, constants, formulas, conditionals, `cond`, simple recursion, pairs, list selectors, one higher-order teaser, and `map`. The beginning looks like this: ```scheme (import (scheme base) (scheme write) (scheme process-context)) (define (square-number n) (* n n)) (define (rectangle-area width height) (* width height)) (check-equal 'procedure-call 81 (square-number 9)) (check-equal 'composed-argument 25 (square-number (+ 2 3))) (check-equal 'rectangle-area 42 (rectangle-area 7 6)) ``` The full source file has a highlighted view at [`/examples/r7rs/intro/lesson-01/`](/examples/r7rs/intro/lesson-01/) and a raw source link from that page. It is currently tested with CHICKEN 5.3.0 and the CHICKEN `r7rs` egg. When working from the source tree used to build this site, run the example suite with: ```sh scripts/test_examples_chicken.sh ``` The expected output includes: ```text Intro lesson 00 tests passed Intro lesson 01 tests passed Intro lesson 02 tests passed LCG tests passed ``` ## Exercises These exercises are original and deliberately small. They are meant to check whether the basic expression, definition, and testing model is clear. - Write an expression that adds `8`, `13`, and `21`. - Write an expression that multiplies the sum of `3` and `4` by the difference of `10` and `6`. - Define a procedure `triple-number` that multiplies its argument by `3`. - Write a check showing that `(triple-number 7)` returns `21`. - Define `square-perimeter`, a procedure that takes a side length and returns the perimeter of a square. - Define `absolute-label`, a procedure that returns the symbol `negative` for numbers below zero and `nonnegative` otherwise. - Write two checks for `absolute-label`, one for a negative input and one for zero. - Define `between-inclusive?`, a predicate that checks whether a value is between a low and high bound. - Use `cond` to write `temperature-label`, returning `freezing`, `cool`, `warm`, or `hot` for numeric temperatures. - Read the `factorial` definition aloud. Which expression rejects invalid input? Which expression is the base case? Which expression is the recursive call? - Compute `(euclid-gcd 84 30)` by hand for two steps, then use the browser evaluator to check the final answer. - Write a quoted proper list containing three symbols. - Use `car` and `cdr` to predict the first element and rest of `'(scheme r7rs lists)`. - Use `cons` to build a pair that is not a proper list, then check it with `pair?` and `list?`. ## What Comes Next The next lesson is [Lesson 2: higher-order procedures](/learn/intro-r7rs/lesson-02/). It takes the small procedure-writing habits from this lesson and uses them to describe reusable patterns: procedures passed as arguments, procedures returned as results, anonymous procedures, and local names. Before moving on, make sure you can explain these ideas without guessing: - Why `(+ 1 2 3)` is a procedure call. - Why `(define (square-number n) (* n n))` defines a procedure. - What formal parameters and actual arguments are. - Why `if` evaluates only one branch. - Why `cond` clauses are not ordinary procedure calls. - Why a recursive procedure needs a base case, and why some procedures also need an invalid-input guard. - Why examples in files should show their imports. - Why a proper list ends in `'()`. - Why `(cons 'left 'right)` is a pair but not a proper list. - Why a small test is better than only reading the result by eye.