Scheme as implemented by Racket (Simple view:) Racket is a version of Scheme. (Full view:) Racket is a platform for implementing and using many languages, and Scheme is one of those that come out of the box. Racket s version of Scheme is somewhat different from the standards, e.g., function names, some features. My slides are right for Racket, but may fail with standard Scheme. But same principles. My slides give you a taste, but there are a lot of useful things I won t cover. 1 / 33
Basic Data Types And Syntax of Their Literals Booleans: #t, #f Numbers: 42 (integers), 1/3 (rational numbers), 42.5 (floating-point numbers), 3+4i (complex numbers) Strings: "hello" Single characters: #\h stands for the letter h. Symbols: User-defined atomic values. You think up a name, put a single-quote in front. Four examples: Firefox Chrome Edge Safari Great for tags, labels, enumerations. Symbols are not strings. You can t append, split, ask for the nth character... 2 / 33
Expressions Literals: Examples on the previous slide. Identifiers: E.g., +, my-angle/time Procedure/Function Applications: (proc param1 param2...) proc and parameters are expressions. proc should evaluate to a procedure. E.g., (sin (/ 0.2 2)) E.g., ((if #t sin cos) (/ 0.2 2)) Special Forms: (keyword expr expr...) For definitions, conditionals, other features... They have their own slides. 3 / 33
Some Boolean Operations (not expr) (and expr expr...) Short-circuits. (and) gives #t. (or expr expr...) Short-circuits. (or) gives #f. (boolean? expr) Tests whether you have a boolean value. 4 / 33
Some Number Operations + - * / max min can take multiple operands. quotient remainder abs sqrt sin cos tan etc. (expt b x) means b x. (exp x) means e x. (log x) means ln x. = < <= > >= can take multiple operands, e.g., (> x y z) means x > y > z. number? tests whether a value is a number. complex?, real?, rational?, integer? test what kind of number. number->string string->number (has ways to indicate parse errors) 5 / 33
Some String Operations (string-length str): length. (string-ref str k): character at position k (start from 0). (substring str i j) like Python s str[i:j]. (substring str i) like str[i:]. (string-append str1 str2...): concatenation. (string-append) gives the empty string. string=? string<? string<=? string>? string>=? Comparisons. Can take multiple operands. There are also case-insensitive versions. string? tests whether a value is a string. 6 / 33
Equality Equality is a mess. Three kinds, each with its intention and but s. eq? Good for booleans and symbols. Pointer equality for most aggregates, e.g., strings, lists. Complicated rules for numbers. Intention: Fast, just compare two machine words. eqv? Good for characters. Complicated rules for numbers, and different from eq?. (Rationale: Choices in treating floating-point s NaN and signed zero.) Most other types: Same as eq?. equal? Structural equality for most aggregates, i.e., comparing contents. 7 / 33
Definitions Give cool names to cool things. Examples: ; A constant. ( define my- width/ height (/ 4 3)) ; A function with 1 parameter. ( define ( greet person) ( string - append " hello " person)) ; A function with 2 parameters. ( define (my- log base x) (/ (log x) (log base))) Recursion is allowed. 8 / 33
Anonymous Functions/Procedures (lambda, λ) (Terminology: procedure expected to be effectful, e.g., I/O; function expected to be effect-free. Not strictly enforced though.) Can write a function without name; procedure expression. (lambda (param1 param2...) body) Example: (lambda (base x) (/ (log x) (log base))) Example usage: ( (lambda (base x) (/ (log x) (log base))) 128 2) Function definition (define (f x y) body) is short-hand for (define f (lambda (x y) body)). May also write λ. 9 / 33
Conditionals If-then-else: (if test then-expr else-expr) Actually test can be non-boolean treated as true. Multiple conditions: (cond [(> x y) (sin x)] [(< x y) (cos y)] [else 0]) If x > y then sin x; else if x < y then cos y; else 0. Test results can be non-boolean treated as true. Furthermore you can obtain and use it: (cond [(+ 4 2) => (lambda (x) (* x x))] [else 0]) This gives 36. 10 / 33
and, or As Conditionals (and expr expr...) Evaluates from left to right, stops as soon as #f happens. Otherwise, the last expr is the answer. Examples: (and 42 #f "hello") gives #f (and 42 hey "hello") gives "hello" (and) gives #t (or expr expr...) Evaluates from left to right, stops as soon as non-#f happens, and that s the answer. Otherwise, the answer is #f. Examples: (or 42 hey "hello") gives 42 (or (and #f) #f) gives #f (or) gives #f 11 / 33
Local Bindings Non-Recursive Local definitions for use in just one expression. (let ([x expr1] [y expr2]) (+ x y (* 2 x y))) Compute x + y + 2xy, where x = expr1 and y = expr2. expr1 and expr2 cannot see the local x or y; they see outer names. (let ([x 3]) (let ([x (* x x)]) ; (* 3 3) x)) gives 9 and is not a recursion. let* allows later bindings to see earlier bindings. (let* ([x 5] [y (+ x 1)]) ; (+ 5 1) (+ x y (* 2 x y))) 12 / 33
Local Bindings Recursive letrec allows recursive bindings (self or mutually). (letrec ([fac (lambda (n) (if (= n 0) 1 (* n (fac (- n 1)))))] [even (lambda (n) (or (= n 0) (not (odd (- n 1)))))] [odd (lambda (n) (not (even (- n 1))))]) (even (fac 5))) 13 / 33
Recommended Code Layout Jokes: Pythonic Scheme inspired by Pythonic Java. Serious: Open parentheses then immediately first word. Procedure definition: Body starts on new line, indented. Long expression: Parts start on new lines, indented. Closing parentheses not on new lines. Most editors have Scheme modes that can do these for you. 14 / 33
Compound Data: Pairs And Lists Cons cell, 2-tuple, pair. Syntax: (cons x y). Can imagine a pair of pointers. Short-hand if both fields are literals: (5. "hello"). What if the 2nd field is (points to) a cons cell again, and its 2nd field is a cons cell again,...? Singly-linked list. Special support for lists: Empty list: () (list x y z) = (cons x (cons y (cons z ()))) List literal: (42 "hi" Chrome) The Chrome means the symbol Chrome. 15 / 33
Some Pair And List Operations Tests: pair?, list?, null? First field of a pair: car Second field of a pair: cdr First item of a list: first, same answer as car, but only for lists. Tail of a list: rest, same answer as cdr, but only for lists. length (list-ref lst i): item at position i. (append lst1 lst2...): concatenation. reverse Higher-order functions: map, filter, foldr, foldl... discussed later. 16 / 33
Compound Data: User-Defined Records (struct dim (width height)) creates a new record type of two fields, with these support utilities: (dim 4 7) constructs a value of this type. dim? tests for this type. dim-width, dim-height: field accessors. struct-copy: Clones a record while replacing some field values. Original record unchanged functional update. (define d1 (dim 4 7)) ( define d2 ( struct - copy dim d1 [ width 5])) Then d2 is (dim 5 7). 17 / 33
Pattern Matching Test for literal, cons cell, or record type, and get the content too. ( struct dim ( width height)) (define (foo x) (match x [ () nada] [(cons b _) b] [(dim w h) (* w h)])) (foo ()) gives nada (foo (1 2 3)) gives 1 (foo (dim 4 7)) gives 28 18 / 33
Input And Output: stdin, stdout, stderr stdout and stdin: (display 5), (display "hello") (newline), (displayln 5), (displayln "hello") (printf "~a = ~a~n" "price" 5) Aside: (format "~a = ~a~n" "price" 5) gives the string rather than outputs it. (read-line) (read-string 10) reads up to the upper bound. If EOF, returns eof, can use eq? or eof-object? to test. stderr: eprintf is like printf but goes to stderr. 19 / 33
Input And Output: Ports Racket has ports, analogous to Java Reader/Writer behind it can be file, string, network connection, message queue, user-defined. (call -with -output -file* "out.txt" #: mode text #: exists replace (lambda (op) ( displayln 6 op) (fprintf op "~a~n" 7))) (let ([s (call -with -input -file* "in.txt" #:mode text (lambda (ip) (read -string 10 ip)))]) (displayln s)) 20 / 33
Sequencing You may want to evaluate multiple expressions (in the order you specify) because the point is their effects. (begin ( displayln " Please enter your name") (read -line)) It returns what the last expression returns. (begin0 expr1 expr2...) returns what expr1 returns. (The other expressions are still evaluated.) (when (> x 0) expr1 expr2...) If true, evaluates the expressions, returns what the last one returns. If false, returns #<void>. 21 / 33
Sequencing Some constructs already support multiple expressions and sequencing, you don t need to wrap with begin: Conditionals and pattern matching: (cond [test expr1 expr2...] [...]...) (match expr [pattern expr1 expr2...] [...]...) Procedure bodies: (define (f x) expr1 expr2...) (lambda (x) expr1 expr2...) Bodies of let etc.: (let ([x 5] [y 42]) expr1 expr2...) Also, and, or already do sequencing. 22 / 33
Mutable Variables (define v 5) (define (f x) (+ v x)) (f 0) ; gives 5 (set! v 6) (f 0) ; gives 6 Mutable pairs, lists, strings, arrays... are also available. Use mutation judiciously. It is much less necessary than most people think. 23 / 33
map (map f (list x y z)) equals (list (f x) (f y) (f z)) Can you write your own version? ( define (my- map f lst) (match lst [ () ()] [(cons hd tl) (cons (f hd) (my-map f tl))])) Remark: Racket s map is more general can take several lists, e.g., (map + (1 2 3) (10 20 30)) equals (11 22 33). 24 / 33
filter (filter number? (9 "4" 0 "1" "6" 5)) equals (9 0 5). Can you write your own version? ( define (my- filter pred lst) (match lst [ () ()] [(cons hd tl) (if (pred hd) ( cons hd (my- filter pred tl)) (my-filter pred tl))])) 25 / 33
foldr Motivation Sum up a list, write your own recursion, first way: ( define (my- sum lst) (match lst [ () 0] [(cons hd tl) (+ hd (my-sum tl))])) Multiply a list, write your own recursion, first way: ( define (my- product lst) (match lst [ () 1] [(cons hd tl) (* hd (my-product tl))])) What is different? What stays the same? Can you factor it out? ( define ( foldr binop z lst) (match lst [ () z] [(cons hd tl) (binop hd (foldr binop z tl))])) 26 / 33
foldr If your function satisfies: (g ()) equals z (g (cons hd tl)) equals (binop hd (g tl)) Then (g lst) equals (foldr binop z lst) Intuitively, they look like a (b (c z)), writing for binop, if the list is (list a b c). Not always obvious. Some refactoring can help. Telltale sign: The recursion is simply on the list tail. Example: Next slide shows why my-map is a foldr. Example: my-filter is also a foldr. 27 / 33
my-map is a foldr = = = (define (my-map f lst) (match lst [ () ()] [(cons hd tl) (cons (f hd) (my-map f tl))])) (define (my-map f lst) (define (g lst) (match lst [ () ()] [(cons hd tl) (cons (f hd) (g tl))])) (g lst)) (define (my-map f lst) (define (binop x r) (cons (f x) r)) (define (g lst) (match lst [ () ()] [(cons hd tl) (binop hd (g tl))])) (g lst)) (define (my-map f lst) (define (binop x r) (cons (f x) r)) (foldr binop () lst)) 28 / 33
foldl Motivation Sum up a list, write your own recursion, second way (accumulator): ( define (my- sum lst) ; ( loop accum lst) computes accum + sum of lst ( define ( loop accum lst) (match lst [ () accum] [(cons hd tl) (loop (+ accum hd) tl)])) (loop 0 lst)) Multiply a list, write your own recursion, first way: ( define (my- product lst) ; ( loop accum lst) computes accum * product of lst ( define ( loop accum lst) (match lst [ () accum] [(cons hd tl) (loop (* accum hd) tl)])) (loop 1 lst)) What is different? What stays the same? Can you factor it out? 29 / 33
foldl ( define ( foldl binop a lst) (match lst [ () a] [(cons hd tl) (foldl binop (binop a hd) tl)])) Intuitively, (foldl binop a (list x y z)) looks like ((a x) y) z, writing for binop. 30 / 33
Procedure-Call Stack (define (f n) (... (f (- n 1))...) (displayln (+ (f 4) (f 1) (f 6))) Control-flow jumps into f; later automagically knows where to return to. Sufficiently elegant solutions are indistiguishable from magic. A stack is used to remember where to return to. Call stack. Benefit: Supports recursion (self and mutual). Price: Each invocation holds up Θ(1) space until it finishes. (Invented by Peter Naur for Algol. Before him, each procedure was given fixed space for return address. Recursion disallowed. People didn t believe Naur when he got this to work.) You will understand this intimately in Part II of the course. 31 / 33
Non-Tail Calls and Tail Calls Non-tail call: There is post-processing after the call returns. ( define (my- sum lst) (match lst [ () 0] [(cons hd tl) (+ hd (my-sum tl))])) Takes Θ(n) space (if n is the list length). Tail call: No post-processing after the call returns. No need to remember return address, just jump. ( define (my- sum lst) ( define ( loop accum lst) (match lst [ () accum] [(cons hd tl) (loop (+ accum hd) tl)])) (loop 0 lst)) Literally a loop. Takes O(1) space. 32 / 33
Nested Lambdas A procedure that returns a procedure. (define map1 (lambda (f) (lambda (lst) (map f lst)))) Sample usage: ((map1 abs) (-1-4)) Real usage: (map (map1 abs) ((-1-4) (-2-5))) Note: (map (map abs)...) doesn t work wrong number of parameters. 33 / 33