Racket編程指南——3 內置的數據類型

3 內置的數據類型

上一章介紹了一些Racket的內建數據類型:數字、布爾值、字符串、列表、和過程。本節爲數據的簡單表提供一個內建數據類型的更完整的覆蓋。

    3.1 布爾值(Boolean)

    3.2 數值(Number)

    3.3 字符(Character)

    3.4 字符串(Unicode)

    3.5 字節(Byte)和字節字符串(Byte String)

    3.6 符號(Symbol)

    3.7 關鍵字(Keyword)

    3.8 點對(Pair)和列表(List)

    3.9 向量(Vector)

    3.10 哈希表(Hash Table)

    3.11 格子(Box)

    3.12 無效值(Void)和未定義值(Undefined)

3.1 布爾值(Boolean)

Racket有表示布爾值的兩個重要的常數:#t表示真,#f表示假。大寫的#T#F解析爲同樣的值,但小寫形式是首選。

boolean?程序識別兩個布爾常量。然而,在對ifcond andor等等的一個測試表達式的結果裏,除了#f之外,任何值都是記爲真。

Examples:
> (= 2 (+ 1 1))

#t

> (boolean? #t)

#t

> (boolean? #f)

#t

> (boolean? "no")

#f

> (if "no" 1 0)

1

3.2 數值(Number)

一個Racket的數值(number)既可以是精確的也可以是不精確的:

  • 一個精確的數值是:

    • 一個任意大的或任意小的整數,比如:599999999999999999-17

    • 一個有理數,它是精確的兩個任意小的或任意大的整數比,比如:1/299999999999999999/2-3/4

    • 一個帶有精確的實部和虛部(即虛部不爲零)的複數,比如:1+2i1/2+3/4i

  • 一個 不精確的數值是:

    • 一個數值的一個IEEE浮點表示,比如:2.03.14e+87,其中IEEE無窮大和一個非數值編寫爲:+inf.0-inf.0+nan.0(或-nan.0);

    • 一個帶有IEEE浮點表示的實部和虛部的複數,比如:2.0+3.0i-inf.0+nan.0i;作爲一種特例,一個帶有一個不精確的虛部的不精確的複數可以有一個精確的零實部。

帶有一個小數點或指數說明符的不精確數字打印,以及作爲整數和分數的精確數字打印。同樣的的慣例申請讀取數值常量,但#e#i能夠前綴一個數值以強制其解析爲一個精確的或不精確的數值。前綴#b#o#x指定二進制、八進制和十六進制數的解釋。

《The Racket Reference(Racket參考)》文檔(4.2 Numbers(數值))有數值的語法的細微之處。

Examples:
> 0.5

0.5

> #e0.5

1/2

> #x03BB

955

包含一個精確數值的計算產生不精確的結果,以致不精確充當了一種數值方面的污染。注意,然而,Racket沒有提供"不精確的布爾值",所以對不精確的數字的比較分支計算卻仍然能產生精確的結果。過程exact->inexactinexact->exact在兩種數值類型之間轉換。

Examples:
> (/ 1 2)

1/2

> (/ 1 2.0)

0.5

> (if (= 3.0 2.999) 1 2)

2

> (inexact->exact 0.1)

3602879701896397/36028797018963968

當精確的結果需要作爲非有理數實數時,不精確的結果也由像sqrtlogsin這樣的過程產生。Racket僅能表示有理數和帶有理數部分的複數。

Examples:
> (sin 0)   ; 有理數...

0

> (sin 1/2) ; 非有理數...

0.479425538604203

在性能而言,帶小整數的計算通常是最快的,其中“小”意味着這個合二爲一的數值小於有符號數值的機器字長。具有非常大的精確整數或具有非整精確數的計算會比不精確數的計算代價要高昂得多。

(define (sigma f a b)
  (if (= a b)
      0
      (+ (f a) (sigma f (+ a 1) b))))

 

> (time (round (sigma (lambda (x) (/ 1 x)) 1 2000)))

cpu time: 75 real time: 117 gc time: 0

8

> (time (round (sigma (lambda (x) (/ 1.0 x)) 1 2000)))

cpu time: 0 real time: 28 gc time: 0

8.0

數值類別整數(integer)有理數(rational)實數(real)(總是有理數)以及複數(complex)用通常的方法定義,並被過程integer?rational?real?以及complex?所驗證。一些數學過程只接受實數,但大多數實現了對複數的標準擴展。

Examples:
> (integer? 5)

#t

> (complex? 5)

#t

> (integer? 5.0)

#t

> (integer? 1+2i)

#f

> (complex? 1+2i)

#t

> (complex? 1.0+2.0i)

#t

> (abs -5)

5

> (abs -5+2i)

abs: contract violation

  expected: real?

  given: -5+2i

> (sin -5+2i)

3.6076607742131563+1.0288031496599335i

=過程爲了數值相等而比較數值。如果給定不精確數和精確數去作比較,它在比較之前從本質上將不精確數轉換爲精確數。相反,eqv?(乃至 equal?)過程比較數值既考慮精確性又考慮數值的相等。

Examples:
> (= 1 1.0)

#t

> (eqv? 1 1.0)

#f

當心涉及不精確數的比較,由於其天性會有出人意料的行爲。甚至實際上簡單的不精確數也許並不意味着你能想到的和他們的意義一致;例如,當一個二進制IEEE浮點數可以精確地表示爲1/2時,它可能近似於1/10

Examples:
> (= 1/2 0.5)

#t

> (= 1/10 0.1)

#f

> (inexact->exact 0.1)

3602879701896397/36028797018963968

《The Racket Reference(Racket參考)》文檔(4.2 Numbers(數值))有關於數值和數值過程的更多內容。

3.3 字符(Character)

Racket 字符(character)對應於Unicode標量值(scalar value)。粗略地說,一個標量值是一個無符號整數,表示爲21位,並且映射到某種自然語言字符或字符塊的某些概念。從技術上講,一個標量值是一個比Unicode標準中的一個“字符”更簡單的概念,但它是一種有許多作用的近似值。例如,任何重音羅馬字母都可以表示爲一個標量值,就像任何普通的漢字字符一樣。

雖然每個Racket字符對應一個整數,但字符數據類型和數值是有區別的。char->integerinteger->char過程在標量值和相應字符之間轉換。

一個可打印字符通常打印爲以#\後跟着代表字符的形式。一個非打印字符通常打印爲以#\u後跟着十六進制數值的標量值的形式。幾個字符以特殊方式打印;例如,空格和換行符分別打印爲#\space#\newline

在《The Racket Reference(Racket參考)》的字符解析文檔有字符語法的更好的知識點。

Examples:
> (integer->char 65)

#\A

> (char->integer #\A)

65

> #\λ

#\λ

> #\u03BB

#\λ

> (integer->char 17)

#\u0011

> (char->integer #\space)

32

display過程直接將一個字符寫入到當前輸出端口(詳見《輸入和輸出》),與用於打印一個字符結果的字符常量語法形成對照。

Examples:
> #\A

#\A

> (display #\A)

A

Racket提供了幾種對字符的分類和轉換的過程。然而,注意某些Unicode字符要只有它們在一個字符串中和一個人所希望的那樣轉換纔行(例如,”ß”的大寫轉換或者”Σ”的小寫轉換)。

Examples:
> (char-alphabetic? #\A)

#t

> (char-numeric? #\0)

#t

> (char-whitespace? #\newline)

#t

> (char-downcase #\A)

#\a

> (char-upcase #\ß)

#\ß

char=?過程比較兩個或多個字符,char-ci=?比較字符但忽略大寫。eqv?equal?過程在字符方面的行爲與char=?表現一樣;當你更具體地聲明正在比較的值是字符時使用char=?

Examples:
> (char=? #\a #\A)

#f

> (char-ci=? #\a #\A)

#t

> (eqv? #\a #\A)

#f

在《The Racket Reference(Racket參考)》的字符部分中提供字符和字符過程的更多信息。

3.4 字符串(Unicode)

一個字符串(string)是一個固定長度的字符(characters)數組。它使用雙引號打印,在字符串中的雙引號和反斜槓字符是用反斜槓轉義。其它普通的字符串轉義被支持,包括\n用於一個換行,\r用於一個回車,使用\後邊跟着多達三個八進制數字實現八進制轉義,以及用\u(多達四位數)實現十六進制轉義。在打印字符串時通常用\u顯示一個字符串中的不可打印字符。

在《Racket參考》中的“讀取字符串(Reading Strings)”文檔有關於字符串語法的更好的知識點。

display過程直接將一個字符串中的字符寫入當前輸出端口(見《輸入和輸出》),在字符串常量語法對比中用於打印一個字符串結果。

Examples:
> "Apple"

"Apple"

> "\u03BB"

"λ"

> (display "Apple")

Apple

> (display "a \"quoted\" thing")

a "quoted" thing

> (display "two\nlines")

two

lines

> (display "\u03BB")

λ

一個字符串可以是可變的也可以是不可變的;作爲表達式直接編寫的字符串是不可變的,但大多數其它字符串是可變的。make-string過程創建一個給定一個長度和可選填充字符的可變字符串。string-ref過程從一個字符串(用基於0的索引)中訪問一個字符。string-set!過程在一個可變字符串中更改一個字符。

Examples:
> (string-ref "Apple" 0)

#\A

> (define s (make-string 5 #\.))
> s

"....."

> (string-set! s 2 #\λ)
> s

"..λ.."

字符串排序和狀態操作通常是區域無關(locale-independent)的;也就是說,它們對所有用戶都採用相同的工作方式。一些區域相關(locale-dependent)的操作被提供,它們允許字符串摺疊和排序的方式取決於最終用戶的區域設置。如果你在排序字符串,例如,如果排序結果應該在機器和用戶之間保持一致,使用string<?string-ci<?,但如果排序純粹是爲一個最終用戶整理字符串,使用string-locale<?string-locale-ci<?

Examples:
> (string<? "apple" "Banana")

#f

> (string-ci<? "apple" "Banana")

#t

> (string-upcase "Straße")

"STRASSE"

> (parameterize ([current-locale "C"])
    (string-locale-upcase "Straße"))

"STRAßE"

對於使用純粹的ASCII、使用原始字節或編碼/解碼Unicode字符串爲字節,使用字節字符串(byte strings)

在《Racket參考》中的字符串(strings)部分提供更多字符串和字符串過程的信息。

3.5 字節(Byte)和字節字符串(Byte String)

一個字節(byte)是一個在0255之間的精確整數。byte?判斷識別表示字節的數字。

Examples:
> (byte? 0)

#t

> (byte? 256)

#f

一個字節字符串(byte string)類似於一個字符串——參見《字符串(Unicode)》,但它的內容是字節序列而不是字符。字節字符串可用於處理純ASCII文本而不是Unicode文本的應用程序中。一個字節字符串的打印形式特別支持這樣使用,因爲一個字節字符串打印像字節字符串的ASCII解碼,但有一個#前綴。在字節字符串中不可打印的ASCII字符或非ASCII字節用八進制表示法編寫。

在《Racket參考》中的“讀取字符串(Reading Strings)”文檔有關於字節字符串語法的更好的知識點。

Examples:
> #"Apple"

#"Apple"

> (bytes-ref #"Apple" 0)

65

> (make-bytes 3 65)

#"AAA"

> (define b (make-bytes 2 0))
> b

#"\0\0"

> (bytes-set! b 0 1)
> (bytes-set! b 1 255)
> b

#"\1\377"

一個字節字符串的display表寫入其原始字節到當前輸出端口(詳見《輸入和輸出》部分)。從技術上講,一個通常(即,字符)的display字符串打印字符串的UTF-8編碼到當前輸出端口,因爲輸出是以字節爲單位的最終定義;然而一個字節字符串的display用無編碼的方式寫入原始字節。按同樣的思路,當這個文檔顯示輸出時,它嚴格說來是顯示輸出的UTF-8編碼格式。

Examples:
> (display #"Apple")

Apple

> (display "\316\273")  ; 等同於"λ"

λ

> (display #"\316\273") ; λ的UTF-8編碼

λ

對於在字符串和字節字符串之間的顯式轉換,Racket直接支持三種編碼:UTF-8,Latin-1和當前的本地編碼。字節到字節轉換(特別是轉換到UTF-8和從UTF-8轉換來)的通用工具彌合了支持任意字符串編碼的差異分歧。

Examples:
> (bytes->string/utf-8 #"\316\273")

"λ"

> (bytes->string/latin-1 #"\316\273")

"λ"

> (parameterize ([current-locale "C"])  ; C局部支持ASCII,
    (bytes->string/locale #"\316\273")) ; 僅僅,這樣……

bytes->string/locale: byte string is not a valid encoding

for the current locale

  byte string: #"\316\273"

> (let ([cvt (bytes-open-converter "cp1253" ; 希臘代碼頁
                                   "UTF-8")]
        [dest (make-bytes 2)])
    (bytes-convert cvt #"\353" 0 1 dest)
    (bytes-close-converter cvt)
    (bytes->string/utf-8 dest))

"λ"

在《Racket參考》裏的“字節字符串(Byte Strings)”部分提供了關於字節字符串和字節字符串函數的更詳盡內容。

3.6 符號(Symbol)

一個符號(symbol)是一個原子值,它像一個前面的標識符那樣以'前綴打印。一個以'開始並帶一個標識符的表達式產生一個符號值。

Examples:
> 'a

'a

> (symbol? 'a)

#t

對於字符的任何序列,正好有一個相應的符號被保留(interned);調用string->symbol過程或者read一個語法標識符,產生一個保留符號。由於保留符號可以被用eq?(或這樣:eqv?equal?)方便地比較,所以它們作爲方便的值用於標籤和枚舉。

符號是區分大小寫的。通過使用一個#ci前綴或其它方式,讀取器能夠被要求去摺疊容器序列以獲得一個符號,但是讀取器通過默認方式保護容器。

Examples:
> (eq? 'a 'a)

#t

> (eq? 'a (string->symbol "a"))

#t

> (eq? 'a 'b)

#f

> (eq? 'a 'A)

#f

> #ci'A

'a

任何字符串(或者說,任何字符序列)都可以提供給string->symbol以獲得對應的符號。對於讀取器輸入來說,任何字符都可以直接出現在一個標識符裏,空白和以下特殊字符除外:

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

實際上,#僅僅不允許在一個符號開始位置,並且也僅僅不允許%在最後位置;除此之外,#是被允許的。此外,.本身不是一個符號。

空格或特殊字符可以通過用|\引用包含進一個標識符裏。這些引用機制用於包含特殊字符或可能額外看起來像數字的標識符的打印表中。

Examples:
> (string->symbol "one, two")

'|one, two|

> (string->symbol "6")

'|6|

在《Racket參考》中的“讀取符號(Reading Symbols)”文檔有關於符號語法的更好的知識點。

write函數打印一個沒有一個'前綴的符號。一個符號的display表與對應的字符串相同。

Examples:
> (write 'Apple)

Apple

> (display 'Apple)

Apple

> (write '|6|)

|6|

> (display '|6|)

6

gensymstring->uninterned-symbol過程生成新的非保留(uninterned)符號,它不等同於(比照eq?)任何先前的保留或非保留符號。非保留符號作爲新標籤是有用的,它不會與其它任何值混淆。

Examples:
> (define s (gensym))
> s

'g42

> (eq? s 'g42)

#f

> (eq? 'a (string->uninterned-symbol "a"))

#f

在《Racket參考》中的“符號(Symbols)”文檔有關於符號的更多信息。

3.7 關鍵字(Keyword)

一個關鍵字(keyword)值類似於一個符號(詳見《符號(Symbol)》),但它的打印形式是用#:進行前綴。

在《Racket參考》裏的“讀取關鍵字”(Reading Keywords)文檔有關於關鍵字的語法更好的知識點。

Examples:
> (string->keyword "apple")

'#:apple

> '#:apple

'#:apple

> (eq? '#:apple (string->keyword "apple"))

#t

更確切地說,一個關鍵字類似於一個標識符;以同樣的方式,一個標識符可以被引用以生成一個符號,一個關鍵字可以被引用以生成一個值。在這兩種情況下都使用同一術語“關鍵字”,但有時我們使用關鍵字值(keyword value)去更具體地針對一個引用關鍵字表達式的結果或使用string->keyword的結果。一個非引用關鍵字不是一個表達式,只是作爲一個非引用標識符,不產生一個符號:

Examples:
> not-a-symbol-expression

not-a-symbol-expression: undefined;

 cannot reference undefined identifier

> #:not-a-keyword-expression

eval:2:0: #%datum: keyword used as an expression

  in: #:not-a-keyword-expression

儘管它們有相似之處,但關鍵字的使用方式不同於標識符或符號。關鍵字是爲了使用(不帶引號)作爲參數列表和在特定的句法形式的特殊標記。運行時的標記和枚舉,而不是關鍵字用符號。下面的示例說明了關鍵字和符號的不同角色。

Examples:
> (define dir (find-system-path 'temp-dir)) ; not '#:temp-dir
> (with-output-to-file (build-path dir "stuff.txt")
    (lambda () (printf "example\n"))
    ; 可選的#:mode參數可以是'text'binary
    #:mode 'text
    ; 可選的#:exists參數可以是'replace'truncate、...
    #:exists 'replace)

3.8 點對(Pair)和列表(List)

一個點對(pair)把兩個任意值結合。cons過程構建點對,carcdr過程分別提取點對的第一和第二個點對元素。pair?判斷識別點對。

一些點對通過圓括號包圍兩個點對元素的打印形式來打印,在開始位置放置一個',並在元素之間放置一個.

Examples:
> (cons 1 2)

'(1 . 2)

> (cons (cons 1 2) 3)

'((1 . 2) . 3)

> (car (cons 1 2))

1

> (cdr (cons 1 2))

2

> (pair? (cons 1 2))

#t

一個列表(list)是一個點對的組合,它創建一個鏈表。更確切地說,一個列表要麼是空列表null,要麼是個點對,其第一個元素是一個列表元素,第二個元素是一個列表。list?判斷識別列表。null?判斷識別空列表。

一個列表通常打印爲一個'後跟一對括號包裹列表元素。

Examples:
> null

'()

> (cons 0 (cons 1 (cons 2 null)))

'(0 1 2)

> (list? null)

#t

> (list? (cons 1 (cons 2 null)))

#t

> (list? (cons 1 2))

#f

當一個列表或點對的其中一個元素不能寫成一個quote(引用)值時,使用listcons打印。例如,一個用srcloc構建的值不能使用quote來編寫,應該使用srcloc來編寫:

> (srcloc "file.rkt" 1 0 1 (+ 4 4))

(srcloc "file.rkt" 1 0 1 8)

> (list 'here (srcloc "file.rkt" 1 0 1 8) 'there)

(list 'here (srcloc "file.rkt" 1 0 1 8) 'there)

> (cons 1 (srcloc "file.rkt" 1 0 1 8))

(cons 1 (srcloc "file.rkt" 1 0 1 8))

> (cons 1 (cons 2 (srcloc "file.rkt" 1 0 1 8)))

(list* 1 2 (srcloc "file.rkt" 1 0 1 8))

也參見list*

如最後一個例子所示,list*是用來縮略一系列不能使用list縮寫的cons

writedisplay函數不帶一個前導'conslistlist*打印一個點對或一個列表。對於一個點對或列表來說writedisplay沒有區別,除非它們運用於列表的元素:

Examples:
> (write (cons 1 2))

(1 . 2)

> (display (cons 1 2))

(1 . 2)

> (write null)

()

> (display null)

()

> (write (list 1 2 "3"))

(1 2 "3")

> (display (list 1 2 "3"))

(1 2 3)

對於列表來說最重要的預定義過程是遍歷列表元素的那些過程:

> (map (lambda (i) (/ 1 i))
       '(1 2 3))

'(1 1/2 1/3)

> (andmap (lambda (i) (i . < . 3))
         '(1 2 3))

#f

> (ormap (lambda (i) (i . < . 3))
         '(1 2 3))

#t

> (filter (lambda (i) (i . < . 3))
          '(1 2 3))

'(1 2)

> (foldl (lambda (v i) (+ v i))
         10
         '(1 2 3))

16

> (for-each (lambda (i) (display i))
            '(1 2 3))

123

> (member "Keys"
          '("Florida" "Keys" "U.S.A."))

'("Keys" "U.S.A.")

> (assoc 'where
         '((when "3:30") (where "Florida") (who "Mickey")))

'(where "Florida")

在《Racket參考》中的“點對和列表(Pairs and Lists)”提供更多有關點對和列表的信息。

點對是不可變的(與Lisp傳統相反),並且pair?list?僅識別不可變的點對和列表。mcons過程創建一個可變點對(mutable pair),它配合set-mcar!set-mcdr!,及mcarmcdr進行操作。一個可變點對用mcons打印,而writedisplay使用{}打印:

Examples:
> (define p (mcons 1 2))
> p

(mcons 1 2)

> (pair? p)

#f

> (mpair? p)

#t

> (set-mcar! p 0)
> p

(mcons 0 2)

> (write p)

{0 . 2}

在《Racket參考》中的“可變點對和列表(Mutable Pairs and Lists)”中提供關於可變點對的更多信息。

3.9 向量(Vector)

一個向量(vector)是任意值的一個固定長度數組。與一個列表不同,一個向量支持常量時間訪問和它的元素更新。

一個向量打印類似於一個列表——作爲其元素的一個括號序列——但一個向量要在'之後加前綴#,或如果它的元素不能用引號表示則使用vector表示。

對於作爲一個表達式的一個向量,可以提供一個可選長度。同時,一個向量作爲一個表達式隱式地爲它的內容quote(引用)這個表,這意味着在一個向量常數中的標識符和括號表代表符號和列表。

在《Racket參考》中的“讀取向量(Reading Vectors)”文檔有向量的語法更好的知識點。

Examples:
> #("a" "b" "c")

'#("a" "b" "c")

> #(name (that tune))

'#(name (that tune))

> #4(baldwin bruce)

'#(baldwin bruce bruce bruce)

> (vector-ref #("a" "b" "c") 1)

"b"

> (vector-ref #(name (that tune)) 1)

'(that tune)

像字符串一樣,一個向量要麼是可變的,要麼是不可變的,向量直接編寫爲表達式是不可變的。

向量可以通過vector->listlist->vector轉換成列表,反之亦然。這種轉換在與對列表的預定義過程相結合中是特別有用的。當分配額外的列表似乎太昂貴時,考慮使用像for/fold的循環表,它像列表一樣識別向量。

Example:
> (list->vector (map string-titlecase
                     (vector->list #("three" "blind" "mice"))))

'#("Three" "Blind" "Mice")

在《Racket參考》中的“向量(vectors)”部分提供有關向量和向量過程的更多內容。

3.10 哈希表(Hash Table)

一個哈希表(hash table)實現了從鍵到值的一個映射,其中鍵和值都可以是任意的Racket值,以及對錶的訪問和更新通常是常量時間操作。鍵的比較使用equal?eqv?eq?,取決於哈希表創建方式是否爲make-hashmake-hasheqvmake-hasheq

Examples:
> (define ht (make-hash))
> (hash-set! ht "apple" '(red round))
> (hash-set! ht "banana" '(yellow long))
> (hash-ref ht "apple")

'(red round)

> (hash-ref ht "coconut")

hash-ref: no value found for key

  key: "coconut"

> (hash-ref ht "coconut" "not there")

"not there"

hashhasheqvhasheq函數從鍵和值的一個初始設置創建不可變哈希表,其中每個值作爲它鍵後邊的一個參數提供。不可變哈希表可用hash-set擴展,它在恆定時間裏產生一個新的不可變哈希表。

Examples:
> (define ht (hash "apple" 'red "banana" 'yellow))
> (hash-ref ht "apple")

'red

> (define ht2 (hash-set ht "coconut" 'brown))
> (hash-ref ht "coconut")

hash-ref: no value found for key

  key: "coconut"

> (hash-ref ht2 "coconut")

'brown

一個原義的不可變哈希表可以通過使用#hash(對基於equal?的表)、#hasheqv(對基於eqv?的表)或#hasheq(對基於eq?的表)編寫爲一個表達式。一個帶括號的序列必須緊跟着#hash#hasheq#hasheqv,其中每個元素是一個帶點的鍵–值對。這個#hash等等這些表都隱含的quote它們的鍵和值的子表。

Examples:
> (define ht #hash(("apple" . red)
                   ("banana" . yellow)))
> (hash-ref ht "apple")

'red

在《Racket參考》的“讀取哈希表(Reading Hash Tables)”文檔有關於哈希表原義的語法更好的知識點。

可變和不可變的哈希表都像不可變哈希表一樣打印,否則如果所有的鍵和值可以用quote表示或者使用hashhasheqhasheqv,那麼使用一個帶引用的#hash#hasheqv#hasheq表。

Examples:
> #hash(("apple" . red)
        ("banana" . yellow))

'#hash(("apple" . red) ("banana" . yellow))

> (hash 1 (srcloc "file.rkt" 1 0 1 (+ 4 4)))

(hash 1 (srcloc "file.rkt" 1 0 1 8))

一個可變哈希表可以選擇性地弱方式(weakly)保留其鍵,因此僅僅只要在其它地方保留鍵,每個映射都被保留。

Examples:
> (define ht (make-weak-hasheq))
> (hash-set! ht (gensym) "can you see me?")
> (collect-garbage)
> (hash-count ht)

0

請注意,只要對應的鍵是可訪問的,即使是一個弱哈希表也會強健地保留它的值。當一個值指回到它的鍵,就造成了一個兩難的依賴,以致這個映射永久被保留。要打破這個循環,映射鍵到一個暫存值(ephemeron),它用它的鍵(除這個哈希表的隱性配對之外)配對值。

在《Racket參考》中的“星曆(ephemerons)”文檔有關於使用ephemerons更好的知識點。

Examples:
> (define ht (make-weak-hasheq))
> (let ([g (gensym)])
    (hash-set! ht g (list g)))
> (collect-garbage)
> (hash-count ht)

1

> (define ht (make-weak-hasheq))
> (let ([g (gensym)])
    (hash-set! ht g (make-ephemeron g (list g))))
> (collect-garbage)
> (hash-count ht)

0

在《Racket參考》中的“哈希表(Hash Tables)”會提供關於哈希表和哈希表過程更多的信息。

3.11 格子(Box)

一個格子(box)是一個單元素向量。它可以打印成一個帶引用的#&後邊跟着這個格子值的打印表。一個#&表也可以用來作爲一個表達,但由於作爲結果的格子是常量,它實際上沒有使用。

Examples:
> (define b (box "apple"))
> b

'#&"apple"

> (unbox b)

"apple"

> (set-box! b '(banana boat))
> b

'#&(banana boat)

在《Racket參考》的“格子(Boxes)”提供關於格子和格子過程的更多信息。

3.12 無效值(Void)和未定義值(Undefined)

某些過程或表達式表不需要一個結果值。例如,display過程被別用僅爲寫輸出的副作用。在這樣的情況下,結果值通常是一個特殊的常量,它打印爲#<void>。當一個表達式的結果是簡單的#<void>時,REPL不打印任何東西。

void過程接受任意數量的參數並返回#<void>。(即,void標識符綁定到一個返回#<void>的過程,而不是直接綁定到#<void>。)

Examples:
> (void)
> (void 1 2 3)
> (list (void))

'(#<void>)

undefined常量,它打印爲#<undefined>,有時是作爲一個參考的結果,其值是不可用的。在Racket以前的版本(6.1以前的版本),過早參考一個局部綁定會產生#<undefined>;相反,現在過早參考會引發一個異常。

在某些情況下,undefined結果仍然可以通過shared表產生。

(define (fails)
  (define x x)
  x)

 

> (fails)

x: undefined;

 cannot use before initialization


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章