1. 程式人生 > >programming-languages學習筆記--第5部分

programming-languages學習筆記--第5部分

programming-languages學習筆記–第5部分

programming-languages學習筆記–第5部分

目錄

1 Racket語法

Racket有非常簡單的語法,term如下:

  • an atom, 例如#t, #f, 34, "hi", null, x,…
  • a special form, 例如define, lambda, if
    • 可以使用巨集定義自己的特殊形式
  • 括號裡的包含的term序列:(t1 t2 … tn)
    • 如果t1是一個特殊形式,序列的語義是特殊的
    • 否則是一個函式呼叫

使用括號把程式文字轉換為程式的樹形表示非常方便和清晰。

Racket中每個括號都是有意義的,不能隨意新增或刪除。 (e)表示無引數呼叫e, ((e))表示無引數呼叫e,並無引數呼叫其返回結果。

2 動態型別

Racket使用動態型別。

只有執行到錯誤程式碼的時候才能發現錯誤。

3 cond

只有#f是false,其餘都是true

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

4 區域性繫結

有4種方法:

  • let
  • let*
  • letrec
  • define

3種let表示式可以出現在任何地方.

let表示式在表示式之前就全部求值。

(define (silly-double x)
  (let ([x (+ x 3)]
        [y (+ x 2)])                    ;這裡的x為引數x
    (+ x y -5)))                        ;等價於(+ x x)

let*表示式在每個之前的繫結環境中求值。

(define (silly-double x)
  (let* ([x (+ x 3)]
         [y (+ x 2)])                   ;這裡的x為+3的x,在上一個繫結的環境中
    (+ x y -8)))

letrec表示式在包括所有繫結的環境中求值。用於相互遞迴,但是表示式還是按照順序求值。

(define (silly-triplee x)
  (letrec ([y (+ x 2)]
           [f (lambda (z) (+ z y w x))]
           [w (+ x 7)])
    (f -9)))

在函式體的開頭使用define,語義和letrec一樣。

5 Toplevel Bindings

檔案中的繫結和letrec類似:

  • 和ML一樣,可以引用之前的繫結
  • 和ML不同,也可以引用之後的繫結
  • 但是隻能在函式體中引用後面的繫結
    • 因為繫結是按順序求值的
    • 使用未定義變數是一個錯誤
  • 和ML不同,不能在模組中定義相同的變數兩次。

6 使用set!

繫結是可變的,改變繫結: (set! x e)

(define b 3)
(define f (lambda (x) (* 1 (+ x b))))
(define c (+ b 4))
(set! b 5)
(define z (f 4))
(define w c)

執行上面的程式,z繫結為9,因為set!改變了b繫結的值。c繫結為7,因為c繫結的值是在set! b前求值的。

如果不想讓f中的b受可能會改變的影響,則在改變發生前製造一個copy.

(define f
  (let ([b b])
    (lambda (x) (* 1 (+ x b)))))

racket中,繫結只能在定義的模組中使用set!修改它。

7 cons

cons製造一個pair,並不是list。以null結尾的巢狀pair是一個list.

cons的單元格是不可變的,mcons可以改變。

(define x (cons 14 null))
(define y x)
(set! x (cons 42 null))
(define fourteen (car y))

set!並沒有修改x指向的舊pair的內容。使用mcons和set-macr!和set-mcdr!可以修改pair內容。

8 延遲求值和Thunks

一個語言構造的關鍵語義問題是它的子表示式是何時求值的。比如在racket中: (e1 e2 … en) 將在求值函式體之前求值函式引數e2, …, en. (lambda (…) …) 直到呼叫函式的時候才求值函式體。注意特殊形式的求值順序有所不同,如if.

使用(lambda () e)達到延遲求值的目的,thunk the argument的意思就是使用(lambda () e)代替e.

9 使用Delay和Force進行惰性求值

惰性求值,按需呼叫,promises. 用於避免重複計算。

(define (my-delay th)
  (mcons #f th))

(define (my-force p)
  (if (mcar p)
      (mcdr p)
      (begin (set-mcar! p #t)
             (set-mcdr! p ((mcdr p)))
             (mcdr p))))

10 streams

流是無限序列值。實現流的方法有很多種,最簡單的可以把流作為一個thunk,呼叫的時候產生一個pair,序列中的第一個元素,表示流的第二個到無限個元素的thunk。

(define ones (lambda () (cons 1 ones)))

11 Memoization

與惰性求值相關的慣用法,並且不適用thunks的是memoization.必須避免副作用,並且同樣的引數返回同樣的結果。

(define (fibonacci1 x)
  (if (or (= x 1) (= x 2))
      1
      (+ (fibonacci1 (- x 1))
         (fibonacci1 (- x 2)))))

12 Macros

巨集定義為語言增加新的語法。它描述瞭如何把新語法轉換為語言已有的不同的語法。巨集系統是定義巨集的語言。macro use只是使用一個之前定義的巨集。macro use的語義是用巨集定義的合適語法替換巨集。這個過程經常叫作巨集展開,因為它很常用,但並不需要語法轉換產生大量程式碼。

關鍵點是巨集展開在我們學過的任何東西之前:在型別檢查之前,在編譯之前,在求值之前。因此巨集可以在任何地方展開,比如函式體內,條件分支內等。

巨集系統用於新增語法糖,等同於用語法糖擴充套件現有語言。

巨集定義,巨集展開。

Tokenization

(define-syntax my-if
  (syntax-rules (then else)
    [(my-if e1 then e2 else e3)
     (if e1 e2 e3)]))
(my-if 2 then 3 else 4)

衛生巨集,巨集展開後的變數名與展開位置的變數名不會混淆。靠詞法作用域。

作者: ntestoc

Created: 2018-12-26 Wed 09:24