programming-languages學習筆記--第5部分
programming-languages學習筆記–第5部分
目錄
- 1. Racket語法
- 2. 動態型別
- 3. cond
- 4. 區域性繫結
- 5. Toplevel Bindings
- 6. 使用set!
- 7. cons
- 8. 延遲求值和Thunks
- 9. 使用Delay和Force進行惰性求值
- 10. streams
- 11. Memoization
- 12. Macros
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)
衛生巨集,巨集展開後的變數名與展開位置的變數名不會混淆。靠詞法作用域。
Created: 2018-12-26 Wed 09:24