Data Type

  1. Numbers: 1, 3.14, 1/2, 6.02e+23, 1+2i, 99999
  2. Booleans: #t, #f
  3. String: "hello world"
  4. Character: #\A
  5. ...

Numbers

#e #i: exact and inexact value

#b #o #x: binary, octal, hexadecimal

; out
> 0.5
0.5

> #e0.5
1/2

> #x03BB
955

Character

#\Space #\newline: special character

#\[CHARACTER]: normal

To display it properly, use display expression

> #\A
#\A

> (display #\A)
A

Identifier

( ) [ ] { } " , ' ` ; # | \

[] and () can be replaceable, Using square brackets in a few key places makes Racket code even more readable.

Standard

  1. the body of a definition is typically indented under the first line of the definition
  2. Identifiers are written immediately after an open parenthesis with no extra space
  3. and closing parentheses never go on their own line

Define

Usage:

bind an id to an value/expression

(define <id> <expr>)

( define ( ‹id› ‹id> ) ‹expr›+ )

In Racket, expression is also a kind of value: \#<procedure:piece>

Also, there can be multi expression after each define, but in this case, only the last expression can return

(define (bake flavor)
  (printf "preheating oven...\n")
  (string-append flavor " pie"))

;;; out
> (bake "apple")
preheating oven...
"apple pie"

id can also point to a function:

(define (twice f v)
  (f (f v)))
 
; out
> (twice sqrt 16)
2

Lambda

( lambda ( ‹id›+ ) ‹expr›+ )

; out
> (twice (lambda (s) (string-append s "!"))
         "hello")
"hello!!"

> (twice (lambda (s) (string-append s "?!"))
         "hello")
"hello?!?!"

Condition

( if ‹expr› ‹expr› ‹expr› )

The first ‹expr› is always evaluated. If it produces a non-#f value, then the second ‹expr› is evaluated for the result of the whole if expression (if), otherwise (else) the third ‹expr› is evaluated for the result.

> (if (> 2 3)
      "2 is bigger than 3"
      "2 is smaller than 3")

;;; out
"2 is smaller than 3"

If nested (multi condition), better using logical expression or and

If include sequence of test, using cond instead.

Cond

( cond {[ ‹expr› ‹expr›+ ]}+ )

In each clause, the first ‹expr› is a test expression. If it produces true, then the clause’s remaining ‹expr›s are evaluated, and the last one in the clause provides the answer for the entire cond expression; the rest of the clauses are ignored.

(define (reply-more s)
  (cond
   [(string-prefix? s "hello ")      ; if
    "hi!"]                           ; then
   [(string-prefix? s "goodbye ")    ; else if
    "bye!"]                          ; then
   [(string-suffix? s "?")           ; else if             
    "I don't know"]                  ; then
   [else "huh?"]))                   ; else
 
;;; out
> (reply-more "hello racket")
"hi!"

> (reply-more "goodbye cruel world")
"bye!"

> (reply-more "what is your favorite color?")
"I don't know"

> (reply-more "mine is lime green")
"huh?"

Notation

(something [id ...+] an-expr ...)

Apply

recusing list, for take two argument expression and list reference, and apply expression to every list element

(define (avg lst)
  (/ (apply + lst) (length lst)))
 
; out
> (avg '(1 2 3))
2

> (avg '(1 2))
3/2

> (avg '(1 2 3 4))
5/2

Local binding

We use Let for local binding

( let ( {[ ‹id› ‹expr› ]}+ ) ‹expr›+ )

> (let ([x (random 4)]
        [o (random 4)])
    (cond
     [(> x o) "X wins"]
     [(> o x) "O wins"]
     [else "cat's game"]))

; out
"O wins"

For the reason we use let instead of define or lambda there is that let is readable and more convenience to bind more than one variable

User defined data type

(struct struct-id (field-id ...))

struct-id : a constructor function that takes as many arguments as the number of field-ids, and returns an instance of the structure type.

(struct posn (x y))

; out
;> (posn 1 2)
;#<posn>

;> (posn? 3)
;#f

;> (posn? (posn 1 2))
;#t

you can access the data in struct by calling -

; out
> (posn-x (posn 1 2))
1

> (posn-y (posn 1 2))
2
  • Struct has no constrain on its data type

Copy and update

struct-copy provide deep copy/update function for struct entity

(struct-copy struct-id struct-expr [field-id expr] ...)

; out
> (define p1 (posn 1 2))
> (define p2 (struct-copy posn p1 [x 3]))
> (list (posn-x p2) (posn-y p2))
'(3 2)

> (list (posn-x p1) (posn-y p1))
'(1 2)

Subtype

similar to inherit in OOP programming language

(struct struct-id super-id (field-id ...))

(struct posn (x y))
(struct 3d-posn posn (z))

However, the subtype cannot access the data from super type directly

; out
> (define p (3d-posn 1 2 3))
> p
;#<3d-posn>

> (posn? p)
;#t

> (3d-posn-z p)
3

; a 3d-posn has an x field, but there is no 3d-posn-x selector:
> (3d-posn-x p)
3d-posn-x: undefined;

 cannot reference an identifier before its definition

  in module: top-level

; use the supertype's posn-x selector to access the x field:
> (posn-x p)
1

Useful pre-defined function

See Racket cheat sheet