Racket notes

4 minute read

Basic

 1#lang racket
 2(provide (all-defined-out))
 3
 4;this is a comment
 5
 6(define s "hello")
 7
 8(define x 3)
 9(define y (+ x 2))
10
11(define cube1
12  (lambda (x)
13    (* x (* x x))))
14
15(define cube2
16  (lambda (x)
17    (* x x x)))
18
19(define (cube3 x)
20  (* x x x))
21
22(define (pow1 x y)
23  (if (=y 0)
24      1
25      (* x (pow1 x (- y 1)))))
26
27; currying
28(define pow2
29  (lambda (x)
30    (lambda (y)
31      (pow1 x y))))

List

  • Empty list: null
    • () doesn"t work for null but '() does
  • build a list: (list e1 ... en)
  • Constructor: cons
  • Access head of list: car
  • Access tail of list: cdr
  • Check for empty: null?

Syntax

A term is either:

  • An atom like #t, #f, 34, "hi", null, 4.0, x,...
  • A special form like define, lambda, if
  • A sequence of terms in parentheses: (t1 t2 t3)
  • Can use [ anything you use (

Remember parentheses matters! For example: (e) means call e with 0 argument.

Dynamic typing

1(define lst (list #t "hi" 1 (list 2 3 4)))

Cond

1(define (sum3 xs)
2  (cond [(null? xs) 0]
3        [(number? (car xs)) (+ (car xs) (sum3 (cdr xs)))]
4        [#t 0]))

What is true?

Anything that is not #f is true #t.

Local bindings

let/let*/letrec

1(let ([x1 e1]
2      [x2 e2]
3      ...
4      [xn en])
5  e)

Racket uses the environment before the let-expression to evaluate e1 e2 ... en, which means if en uses x1, x2, that would mean some outer variables of the same name. Instead, the expressions in let* are evaluated in the environment produced from the previous bindings (later ones shadow) .

The expressions in letrec are evaluated in the environment that includes all th bindings. It is needed for mutual recursion.

set!

Racket has assignment statements:

1(set! x e)

Once you have side-effects, sequences are useful:

1(begin e1 e2 e3)

cons/mcons

cons produces pairs or lists. (Actually lists are just extented pairs)

1(define pr (cons 1 (cons #t "hi"))) ; is a pair
2(define lst (cons 1 (cons #t (cons "hi" null)))) ; is a list

mcons is another way to make pairs which allows you to change the value inside piars:

1(define mpr (mcons 1 (mcons #t "hi")))
2(mcar mpr) ; 1
3(mcdr mpr) ; (mcons (#t "hi"))
4(set-mcdr! mpr 47) ; mpr becomes (mcons 1 47)

Related form:

  • mcons
  • mcar
  • mcdr
  • mpair?
  • set-mcar!
  • set-mcdr!

Delayed Evaluation and Thunk

In most programming languages, given e1 e2 ... en, the function arguments e2, ..., en are evaluated once before the function body is executed. So if we define a function like:

1(define (my-if-bad x y z) (if x y z))
2
3(define (factorial-wrong x)
4  (my-if-bad (= x 0)
5             1
6             (* x (factorial-wrong (- x 1)))))

if we use if instead of my-if-bad, factorial-wrong acts just like we want. But with my-if-bad, the function never stops because the two branches evaluate at the same time.

Thanks to lambda, we can delay the evaluation, using the fact that function bodies are not evaluated until the function gets called.

1(define (my-if x y z) (if x (y) (z)))
2
3(define (factorial x)
4  (my-if (= x 0)
5         (lambda () 1)
6         (lambda () (* x (factorial (- x 1))))))

The general idiom of using a zero-argument function to delay evaluation is also called a thunk (or, thunk the argument).

By the way,

Lazy-evaluation/Call-by-need/Promises

Streams

A stream is an infinite sequence of values.

 1#lang racket
 2; 1 1 1 1 ...
 3(define ones (lambda () (cons 1 ones)))
 4
 5; 1 2 3 4 ...
 6(define nats
 7  (letrec ([f (lambda (x) (cons x (lambda () (f (+ x 1)))))])
 8     (lambda () (f 1))))
 9
10; 2 4 6 8 ...
11(define power-of-two
12  (letrec ([f (lambda (x) (cons x (lambda () (f (* x 2)))))])
13    (lambda () (f 2))))
14
15; higher-order maker
16(define (stream-maker fn arg)
17  (letrec ([f (lambda (x) (cons x (lambda () (f (fn x arg)))))])
18    (lambda () (f arg))))

Memoization (Not Memorization)

Memoization is another idiom related to lazy evaluation that does not actually use thunks. To implement memoization we do use mutation: Whenever the function is called with an argument we have not seen before, we compute the answer and then add the result to the table.

 1(define fibonacci
 2  (letrec([memo null]
 3          [f (lambda (x)
 4               (let ([ans (assoc x memo)])
 5                 (if ans
 6                     (cdr ans)
 7                     (let ([new-ans (if (or (= x 1) (= x 2))
 8                                        1
 9                                        (+ (f (- x 1))
10                                           (f (- x 2))))])
11                       (begin
12                         (set! memo (cons (cons x new-ans) memo))
13                         new-ans)))))])
14    f))

Macros

Think about these things about macros and how Racket handles them better than other macro systems(notably C/C++)

  • Tokenization
  • Parenthesization
  • Scope

Syntax

1(define-syntax myif
2  (syntax-rules (then else)
3    [(my-if e1 then e2 else e3)
4     (if e1 e2 e3)]))
5
6(define-syntax my-delay
7  (syntax-rules ()
8    [(my-delay e)
9     (mcons #f (lambda () e))]))

Hygiene

Recursive Datatypes Via Rackets’s struct

https://docs.racket-lang.org/reference/define-struct.html?q=struct#%28form._%28%28lib._racket%2Fprivate%2Fbase..rkt%29._struct%29%29

1(struct foo (bar baz quux) #:transparent