On this page:
3.1 變數與函數(variable and function)
3.1.1 exercise
3.2 conditional:   if/  cond/  case/  match
3.2.1 if
3.2.2 cond
3.2.3 case
3.2.4 match
3.3 let/  let*/  letrec
8.6

3 語法與計算規則

語法(syntax)和計算規則(computation rule)經常會被混為一談,但他們是不同的,例如一個 Forth 的函數呼叫可能寫成

1 2 3 foo

Racket 寫成

(foo 1 2 3)

但計算規則卻是一樣的:1 2 3foo 的參數,結果是由 foo 的內容決定。於是我們大致可以理解計算規則跟語法為什麼不盡然有關聯了,其實還有很多細節,但對一個 Racket 的入門教學這樣暫時也就夠了。

3.1 變數與函數(variable and function)

函數呼叫(function call)大概是最普遍的計算規則,在各種語言裡面都可以看到,它也經常被稱為 application,因此函數也可以被稱為 applicable。在 Racket 裡,application 就是一個 list,第一個元素被當成函數(所以如果放入不是函數的東西會出現錯誤),剩下的被當成參數。到這裡,Racket 的計算規則暫時可以理解成被 quote 包含的會整塊被當成一個值,剩下的會被當成 application。

有了函數呼叫,自然有定義函數的方式,Racket 提供了 define form 來做這件事

> (define (fib n)
    (case n
      [(0) 1]
      [(1) 1]
      [else (+ (fib (- n 1)) (fib (- n 2)))]))
> (fib 1)

1

> (fib 2)

2

> (fib 3)

3

> (fib 4)

5

> (fib 5)

8

我們也可以把 fib 改寫成 define 的變數形式

(define fib
  (lambda (n)
    (case n
      [(0) 1]
      [(1) 1]
      [else (+ (fib (- n 1)) (fib (- n 2)))])))

到這裡,我們知道 Racket 除了 quote 跟 application 的第三種規則:form。內建的 form 有很多,而我們可以用 define-syntax 等 form 定義更多 form,這就是為什麼 Racket 要讓資料跟程式具有同像性,因為如此一來處理程式就如處理資料一般容易。

接下來,我們來了解更多常見的 form 吧!

3.1.1 exercise

下面是關於 define 的練習

3.2 conditional: if/cond/case/match

3.2.1 if

if form 是各語言常見的語法,以下是一個案例

(if (= x 1)
  'x-is-one
  'x-is-not-one)

syntax

(if test-expr then-expr else-expr)

我們可以看到 if 有三大元素:

也就是

唯一需要特別注意的是 racket 的 if 不能省略 else-expr(跟某些語言不一樣)。不過如果有這種需要,可以改用 when 或是 unless form。

3.2.2 cond

syntax

(cond cond-clause ...)

 
cond-clause = [test-expr then-body ...+]
  | [else then-body ...+]
  | [test-expr => proc-expr]
  | [test-expr]
cond form 可以看作是 if 的推廣。例子如下:

(cond
  [(= x 1) 'x-is-one]
  [(= x 2) 'x-is-two]
  [(> x 2) 'x-is-more-than-two]
  [else    'something-else]) ; else 是可選的

它按順序測試任意數量的 test-expr (i.e. (= x 1)),每個 test-expr 又對應一個表達式 (e.g. 'x-is-one),每組這樣的 sexp 稱為一個 cond-clause (e.g. [(= x 1) 'x-is-one])。最後一個 cond-clause 中 test-case 可換成 else 來匹配任意情況。

改寫成 if form 的形式相當於

(if (= x 1)
  'x-is-one
  (if (= x 2)
    'x-is-two
    (if (> x 2) ;  test-case  else 去掉的話,這行應該就是 (when (> x 2) 'x-is-more-than-two)
      'x-is-more-than-two
      'something-else)))

[test-expr then-body ...+]

一般的 clause 會在 test-expr 為 #true 時執行

[else then-body ...+]

else 是特殊的一個 clause,它表示預設的處理邏輯

[test-expr => proc-expr]
[test-expr]

3.2.3 case

syntax

(case val-expr case-clause ...)

 
case-clause = [(datum ...) then-body ...+]
  | [else then-body ...+]
case form 跟 C 語言的 switch case 語法很像,例子如下:

(case x
  [(1)   'x-is-one]
  [(2 3) 'x-is-two-or-three]
  [else  'something-else]) ; else 是可選的

不妨把 x 位置的表達式稱為 target。方括號的 sexp 稱為 case-clause(e.g. [(2 3) 'x-is-two-or-three]),case-clause 中左手邊的則是一個列表(e.g. '(2 3) 或者 (list 2 3)),檢查 x 是否在列表中,返回對應的表達式(e.g. 'x-is-two-or-three),不考慮性能的情況下改寫成 cond form 的形式相當於

(cond
  [(or (equal? x 1))              'x-is-one]
  [(or (equal? x 2) (equal? x 3)) 'x-is-two-or-three] ; Racket 會把這行優化成 O(log N)
  [else                           'something-else])
3.2.4 match

syntax

(match val-expr clause ...)

 
clause = [pat body ...+]
  | [pat (=> id) body ...+]
  | [pat #:when cond-expr body ...+]
match form 跟剛剛的 case form 長很十分像。只是 target 匹配的不是列表中的元素,而是 pattern。例子如下:

pattern 可以是 constructor 和字面值的組合。
> (match 3
    [1 'target-is-one]
    [2 'target-is-two]
    [3 'target-is-three])

'target-is-three

> (match '(a b c)
    ['(a a a)     "target is a list of three a"]
    [(list a b)   "target is a list of a and b"]
    ['(a b c)     "target is a list of a, b and c"])

"target is a list of a, b and c"

> (match '(a . b)
    [(list a b) 'list]
    [(cons a b) 'pair])

'pair

pattern 可以是 identifier 或 _。當 pattern 是 identifier 或 _ 時,匹配任意值。
> (define x 0)
> (define y 1)
> (match x
    [10 'x-is-ten]
    [_  'x-is-not-ten])

'x-is-not-ten

> (match y
    [x "pattern variable is not the same x as the one we defined at begining"]
    [1 "y is one"])

"pattern variable is not the same x as the one we defined at begining"

> (match 2
    [0 "zero"]
    [1 "one"]
    [x (format "mismatch with value ~s" x)])

"mismatch with value 2"

pattern 可以是 constructor 和 pattern 的組合(nested)。
> (match '(a b)
    [(list 'a x) (format "the second element is ~s" x)]
    [(list  a b) "match but not reach"])

"the second element is b"

> (match '(a b)
    ['(a x)  "(list 'a 'b) != (list 'a 'x)"]
    ['(a b)  "(list 'a 'b) == (list 'a 'b)"])

"(list 'a 'b) == (list 'a 'b)"

> (match '(a (b (c d)))
    [(list 'a (list 'b res)) res])

'(c d)

pattern 中 某個 sub-pattern 的右方插入 ... 代表該 sub-pattern 可以有任意多個。
> (match '(1 1 1)
    [(list 1 ...) 'ones]
    [_ 'other])

'ones

> (match '(1 1 2)
    [(list 1 ...) 'ones]
    [_ 'other])

'other

> (match '(1 2 3 4)
    [(list 1 x ... 4) x])

'(2 3)

> (match (list (list 'a 23 'b) (list 'c 22 'd))
    [(list (list x y z) ...) (apply + y)])

45

3.3 let/let*/letrec

let 是一種綁定語法,如下:

(let ([x 1]
      [y 2])
  (+ x y))

上面的語法等同於:

((lambda (x y)
  (+ x y))
 1 2)

但注意這僅僅是計算上等價,如果牽涉到副作用、類型與 continuation 等更複雜的模型,上面的轉換並非永遠成立。而 let*let 的擴充,表示後面的變數可以依賴前面的變數,如下語法:

(let* ([x 1]
       [y x])
  y)

可以視為

(let ([x 1])
  (let ([y x])
    y))

最後則是 letrec,這次所有變數都可以互相依賴,因此可以寫出遞迴的定義。一般來說前面兩個都建議用 define 取代,而 letrec 並不行。