2 Racket概要

本章提供了一个对Racket的快速入门作为给这个指南余下部分的背景。有一些Racket经验的读者可以直接跳到《内置的数据类型》部分。

2.1 简单的值

2.2 简单的定义和表达式

2.2.1 定义

2.2.2 缩进代码的提示

2.2.3 标识

2.2.4 函数调用(过程应用程序)

2.2.5 带if、and、or和cond的条件句

2.2.6 函数重复调用

2.2.7 匿名函数与lambda

2.2.8 用define、let和let*实现局部绑定

2.3 列表、迭代和递归

2.3.1 预定义列表循环

2.3.2 从头开始列表迭代

2.3.3 尾递归

2.3.4 递归和迭代

2.4 pair、list和Racket的语法

2.4.1 用quote引用pair和symbol

2.4.2 使用'缩写quote

2.4.3 列表和Racket语法

2.1 简单的值

Racket值包括数值、布尔值、字符串和字节字符串。在DrRacket和文档示例中(当你在着色状态下阅读文档时),值表达式显示为绿色。

数值(number)以通常的方式书写,包括分数和虚数:

数值(Number) (later in this guide) explains more about 数值(Numbers).

1       3.14
1/2     6.02e+23
1+2i    9999999999999999999999

布尔值(boolean)用#t表示真,用#f表示假。然而,在条件从句中,所有非#f值被视为真。

布尔值(Boolean) (later in this guide) explains more about 布尔值(boolean).

字符串(string)写在双引号("")之间。在一个字符串中,反斜杠(/)是一个转义字符;例如,一个反斜杠之后的一个双引号包括了字符串中的一个字面上的双引号。除了一个保留的双引号或反斜杠,任何Unicode字符都可以在字符串常量中出现。

字符串(Unicode) (later in this guide) explains more about 字符串(string).

"Hello, world!"
"Benjamin \"Bugsy\" Siegel"
"λx:(μα.α→α).xx"

当一个常量在REPL中被求值时,它通常打印与输入语法相同的结果。在某些情况下,打印格式是输入语法的一个标准化版本。在文档和在DrRacket的REPL中,结果打印为蓝色而不是绿色以突出打印结果与输入表达式之间的区别。

Examples:

> 1.0000

1.0

> "Bugs \u0022Figaro\u0022 Bunny"

"Bugs \"Figaro\" Bunny"

2.2 简单的定义和表达式

一个程序模块一般被写作

#lang ‹langname› ‹topform›*

topform›既是一个‹definition›也是一个‹expr›。REPL也对‹topform›求值。

在语法规范里,文本使用灰色背景,比如#lang,代表文本。除了(、)及[、]之前或之后不需要空格之外,文本与非结束符(像‹ID›)之间必须有空格。注释以;开始,直至这一行结束,空白也做相同处理。

《Racket参考》中的“(parse-comment)”提供有更多的有关注释的不同形式内容。

以后的内容遵从如下惯例:*在程序中表示零个或多个前面元素的重复,+表示一个或多个前面元素的重复,{} 组合一个序列作为一个元素的重复。

2.2.1 定义

表的一个定义:

定义:define (later in this guide) explains more about 定义.

( define ‹id› ‹expr› )

绑定‹id›到‹expr›的结果,而

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

绑定第一个‹id›到一个函数(也叫一个程序),它通过余下的‹id›以参数作为命名。在函数情况下,该‹expr›是函数的函数体。当函数被调用时,它返回最后一个‹expr›的结果。

Examples:
(define pie 3)             ; 定义pie为3
(define (piece str)        ; 定义piece为一个
  (substring str 0 pie))   ; 带一个参数的函数
> pie

3

> (piece "key lime")

"key"

在底层,一个函数定义实际上与一个非函数定义相同,并且一个函数名不是不需在一个函数调用中使用。一个函数只是另一种类型的值,尽管打印形式不一定比数字或字符串的打印形式更完整。

Examples:
> piece

#<procedure:piece>

> substring

#<procedure:substring>

一个函数定义能够包含函数体的多个表达式。在这种情况下,在调用函数时只返回最后一个表达式的值。其它表达式只对一些副作用进行求值,比如打印。

Examples:
(define (bake flavor)
  (printf "pre-heating oven...\n")
  (string-append flavor " pie"))
> (bake "apple")

pre-heating oven...

"apple pie"

Racket程序员更喜欢避免副作用,所以一个定义通常只有一个表达式。然而,重要是去懂得多个表达式在一个定义体内是被允许的,因为它解释了为什么以下nobake函数未在其结果中包含它的参数:

(define (nobake flavor)
  string-append flavor "jello")
> (nobake "green")

"jello"

在nobake中,没有圆括号在string-append flavor "jello"周围,那么它们是三个单独的表达式而不是一个函数调用表达式。表达式string-append和flavor被求值,但结果从未被使用。相反,该函数的结果仅是最终那个表达式的结果,"jello"。

2.2.2 缩进代码的提示

换行和缩进对于解析Racket程序来说并不重要,但大多数Racket程序员使用一套标准的约定来使代码更易读。例如,一个定义的主体通常在这个定义的第一行下面缩进。标识是在一个没有额外空格的括号内立即写出来的,而闭括号则从不自己独立一行。

当你在一个程序或REPL表达式里键入Enter(回车)键,DrRacket会根据标准风格自动缩进。例如,如果你在键入(define (greet name)后面敲击Enter,那么DrRacket自动为下一行插入两个空格。如果你改变了一个代码区域,你可以在DrRacket里选择它并敲击Tab键,那么DrRacket将重新缩进代码(没有插入任何换行)。象Emacs这样的编辑器提供一个带类似缩进支持的Racket或Scheme模式。

重新缩进不仅使代码更易于阅读,它还会以你希望的方式给你更多的反馈,象你的括号是否匹配等等。例如,如果你在给一个函数的最后参数之后遗漏了一个闭括号,则自动缩进在第一个参数下开始下一行,而不是在"define"关键字下:

(define (halfbake flavor
                  (string-append flavor " creme brulee")))

在这种情况下,缩进有助于突出错误。在其它情况下,当一个开括号没有匹配的闭括号,在缩进的地方可能是正常的,racket和DrRacket都使用源程序的缩进去提示一个括号可能丢失的地方。

2.2.3 标识

Racket对标识的语法是特别自由的。但排除以下特殊字符。

标识和绑定 (later in this guide) explains more about 标识.

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

同时除了产生数字常数的字符序列,几乎任何非空白字符序列形成一个‹id›。例如substring是一个标识。另外,string-append和a+b是标识,而不是算术表达式。这里还有几个更多的例子:

+
Hfuhruhurr
integer?
pass/fail
john-jacob-jingleheimer-schmidt
a-b-c+1-2-3

2.2.4 函数调用(过程应用程序)

我们已经看到过许多函数调用,更传统的术语称之为过程应用程序。函数调用的语法是:

函数调用 (later in this guide) explains more about 函数调用.

( ‹id› ‹expr›* )

expr›的个数决定了提供给由‹id›命名的函数的参数个数。

racket语言预定义了许多函数标识,比如substringstring-append。下面有更多的例子。

在贯穿于整个文档的示例Racket代码中,预定义的名称的使用被链接到了参考手册(reference manual)。因此,你可以单击一个标识来获得关于其使用的完整详细资料。

> (string-append "rope" "twine" "yarn")  ; 添加字符串

"ropetwineyarn"

> (substring "corduroys" 0 4)            ; 提取子字符串

"cord"

> (string-length "shoelace")             ; 获取字符串长度

8

> (string? "Ceci n'est pas une string.") ; 识别字符串

#t

> (string? 1)

#f

> (sqrt 16)                              ; 找一个平方根

4

> (sqrt -16)

0+4i

> (+ 1 2)                                ; 数字相加

3

> (- 2 1)                                ; 数字相减

1

> (< 2 1)                                ; 数字比较

#f

> (>= 2 1)

#t

> (number? "c'est une number")           ; 识别数字

#f

> (number? 1)

#t

> (equal? 6 "half dozen")                ; 任意比较

#f

> (equal? 6 6)

#t

> (equal? "half dozen" "half dozen")

#t

2.2.5 带ifandorcond的条件句

接下来最简单的表达式是if条件句:

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

条件 (later in this guide) explains more about 条件句.

第一个‹expr›总是被求值。如果它产生一个非#f值,那么第二个‹expr›被求值为整个if表达式的结果,否则第三个‹expr›被求值为结果。

Example:
> (if (> 2 3)
      "bigger"
      "smaller")

"smaller"

(define (reply s)
  (if (equal? "hello" (substring s 0 5))
      "hi!"
      "huh?"))
> (reply "hello racket")

"hi!"

> (reply "λx:(μα.α→α).xx")

"huh?"

复合的条件句可以由嵌套的if表达式构成。例如,当给定非字符串(non-strings)时,你可以编写reply函数来工作:

(define (reply s)
  (if (string? s)
      (if (equal? "hello" (substring s 0 5))
          "hi!"
          "huh?")
      "huh?"))

代替重复"huh?"事例,这个函数这样写会更好:

(define (reply s)
  (if (if (string? s)
          (equal? "hello" (substring s 0 5))
          #f)
      "hi!"
      "huh?"))

但这些嵌套的if很难阅读。Racket通过andor表提供了更易读的快捷表示,它可以和任意数量的表达式搭配:

组合测试:andor (later in this guide) explains more about and and or.

( and ‹expr›* )
( or ‹expr›* )

and表绕过情况:当一个表达式产生#f,它停止并返回#f,否则它继续运行。当or表遇到一个真的结果时,它同样的产生绕过情况。

Examples:
(define (reply s)
  (if (and (string? s)
           (>= (string-length s) 5)
           (equal? "hello" (substring s 0 5)))
      "hi!"
      "huh?"))
> (reply "hello racket")

"hi!"

> (reply 17)

"huh?"

嵌套if的另一种常见模式涉及测试的一个序列,每个测试都有自己的结果:

(define (reply-more s)
  (if (equal? "hello" (substring s 0 5))
      "hi!"
      (if (equal? "goodbye" (substring s 0 7))
          "bye!"
          (if (equal? "?" (substring s (- (string-length s) 1)))
              "I don't know"
              "huh?"))))

对测试的一个序列的快捷形式是cond表:

编链测试:cond (later in this guide) explains more about cond.

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

一个cond表包含了括号之间的从句的一个序列。在每一个从句中,第一个‹expr›是一个测试表达式。如果它产生真值,那么从句的剩下‹expr›被求值,并且从句中的最后一个提供整个cond表达的答案,其余的从句被忽略。如果这个测试 ‹expr›产生#f,那么从句的剩余‹expr›被忽视,并继续下一个从句求值。最后的从句可以else作为一个#t测试表达式的同义词使用。

使用cond,reply-more函数可以更清楚地写成如下形式:

(define (reply-more s)
  (cond
   [(equal? "hello" (substring s 0 5))
    "hi!"]
   [(equal? "goodbye" (substring s 0 7))
    "bye!"]
   [(equal? "?" (substring s (- (string-length s) 1)))
    "I don't know"]
   [else "huh?"]))
> (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?"

对于cond从句的方括号的使用是一种惯例。在Racket中,圆括号和方括号实际上是可互换的,只要(匹配)或[匹配]即可。在一些关键的地方使用方括号使Racket代码更易读。

2.2.6 函数重复调用

在我们早期的函数调用语法中,我们过分简单化了。一个函数调用的实际语法允许一个对这个函数的任意表达式,而不是仅仅一个‹id›:

函数调用 (later in this guide) explains more about 函数调用.

( ‹expr› ‹expr›* )

第一个‹expr›常常是一个‹id›,比如string-append+,但它可以是对一个函数的求值的任意情况。例如,它可以是一个条件表达式:

(define (double v)
  ((if (string? v) string-append +) v v))
> (double "mnah")

"mnahmnah"

> (double 5)

10

在语句构成上,在一个函数调用的第一个表达甚至可以是一个数值——但那会导致一个错误,因为一个数值不是一个函数。

> (1 2 3 4)

application: not a procedure;

expected a procedure that can be applied to arguments

given: 1

当你偶然忽略了一个函数名或在你使用额外的圆括号围绕一个表达式时,你最常会得到一个像“expected a procedure”这样的一条错误。

2.2.7 匿名函数与lambda

如果你不得不命名你所有的数值,那Racket中的编程就太乏味了。代替(+ 1 2)的写法,你不得不这样写:

lambda函数(过程) (later in this guide) explains more about lambda.

> (define a 1)
> (define b 2)
> (+ a b)

3

事实证明,要命名所有你的函数也可能是很乏味的。例如,你可能有一个函数 twice,它带了一个函数和一个参数。如果你已经有了这个函数的名字,那么使用 twice是比较方便的,如sqrt

(define (twice f v)
  (f (f v)))
> (twice sqrt 16)

2

如果你想去调用一个尚未定义的函数,你可以定义它,然后将其传递给twice:

(define (louder s)
  (string-append s "!"))
> (twice louder "hello")

"hello!!"

但是如果对twice的调用是唯一使用louder的地方,却还要写一个完整的定义是很可惜的。在Racket中,你可以使用一个lambda表达式去直接生成一个函数。lambda表后面是函数参数的标识,然后是函数的主体表达式:

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

通过自身求值一个lambda表产生一个函数:

> (lambda (s) (string-append s "!"))

#<procedure>

使用lambda,上述对twice的调用可以重写为:

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

"hello!!"

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

"hello?!?!"

lambda的另一个用途是作为一个生成函数的函数的一个结果:

(define (make-add-suffix s2)
  (lambda (s) (string-append s s2)))
> (twice (make-add-suffix "!") "hello")

"hello!!"

> (twice (make-add-suffix "?!") "hello")

"hello?!?!"

> (twice (make-add-suffix "...") "hello")

"hello......"

Racket是一个词法作用域(lexically scoped)语言,这意味着函数中的s2总是通过make-add-suffix引用创建该函数调用的参数返回。换句话说,lambda生成的函数“记住”了右边的s2:

> (define louder (make-add-suffix "!"))
> (define less-sure (make-add-suffix "?"))
> (twice less-sure "really")

"really??"

> (twice louder "really")

"really!!"

我们有了对表(define ‹id› ‹expr›)的定义的一定程度的引用作为“非函数定义(non-function definitions)“。这种表征是误导性的,因为‹expr›可以是一个lambda表,在这种情况下,定义与使用“函数(function)”定义表是等价的。例如,下面两个louder的定义是等价的:

(define (louder s)
  (string-append s "!"))
(define louder
  (lambda (s)
    (string-append s "!")))
> louder

#<procedure:louder>

注意,对第二例子中louder的表达式是用lambda写成的“匿名”函数,但如果可能的话,无论如何,编译器推断出一个名称以使打印和错误报告尽可能地提供信息。

2.2.8 用defineletlet*实现局部绑定

现在是在我们的Racket语法中收回另一个简化的时候了。在一个函数的主体中,定义可以出现在函数主体表达式之前:

内部定义 (later in this guide) explains more about 局部(内部)定义.

( define ( ‹id› ‹id›* ) ‹definition›* ‹expr›+ )
( lambda ( ‹id›* ) ‹definition›* ‹expr›+ )

在一个函数主体的开始的定义对这个函数主体来说是局部的。

Examples:
(define (converse s)
  (define (starts? s2) ; local to converse
    (define len2 (string-length s2))  ; local to starts?
    (and (>= (string-length s) len2)
         (equal? s2 (substring s 0 len2))))
  (cond
   [(starts? "hello") "hi!"]
   [(starts? "goodbye") "bye!"]
   [else "huh?"]))
> (converse "hello!")

"hi!"

> (converse "urp")

"huh?"

> starts? ; outside of converse, so...

starts?: undefined;

cannot reference an identifier before its definition

in module: top-level

创建局部绑定的另一种方法是let表。let的一个优势是它可以在任何表达式位置使用。另外,let可以一次绑定多个标识,而不是每个标识都需要一个单独的define

内部定义 (later in this guide) explains more about let and let*.

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

每个绑定从句是一个‹id›和一个‹expr›通过方括号包围,并且这个从句之后的表达式是let的主体。在每一个从句里,为了在主题中的使用,该‹id›被绑定到‹expr›的结果。

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

"O wins"

一个let表的绑定仅在let的主体中可用,因此绑定从句不能互相引用。相比之下,let*表允许后面的从句使用更早的绑定:

> (let* ([x (random 4)]
         [o (random 4)]
         [diff (number->string (abs (- x o)))])
    (cond
     [(> x o) (string-append "X wins by " diff)]
     [(> o x) (string-append "O wins by " diff)]
     [else "cat's game"]))

"cat's game"

2.3 列表、迭代和递归

Racket语言是Lisp语言的一种方言,名字来自于“LISt Processor”。内置的列表数据类型保留了这种语言的一个显著特征。

list函数接受任意数量的值并返回一个包含这些值的列表:

> (list "red" "green" "blue")

'("red" "green" "blue")

> (list 1 2 3 4 5)

'(1 2 3 4 5)

一个列表通常用'打印,但是一个列表的打印形式取决于它的内容。更多信息请看《点对(Pair)和列表(List)》。

就如你能够看到的那样,一个列表结果在REPL中打印为一个引用',并且采用一对圆括号包围这个列表元素的打印表。这里有一个容易混淆的地方,因为两个表达式都使用圆括号,比如(list "red" "green" "blue"),那么打印结果为'("red" "green" "blue")。除了引用,结果的圆括号在文档中和在DrRacket中打印为蓝色,而表达式的圆括号是棕色的。

在列表方面有许多预定义的函数操作。下面是少许例子:

> (length (list "hop" "skip" "jump"))        ; count the elements

3

> (list-ref (list "hop" "skip" "jump") 0)    ; extract by position

"hop"

> (list-ref (list "hop" "skip" "jump") 1)

"skip"

> (append (list "hop" "skip") (list "jump")) ; combine lists

'("hop" "skip" "jump")

> (reverse (list "hop" "skip" "jump"))       ; reverse order

'("jump" "skip" "hop")

> (member "fall" (list "hop" "skip" "jump")) ; check for an element

#f

2.4 pair、list和Racket的语法

cons函数实际上接受任意两个值,而不只是一个给第二个参数的列表。当第二个参数不是empty且不是自己通过cons产生的时,结果以一种特殊的方式打印出来。两个值用cons凑在一起被打印在括号之间,但在两者之间有一个点(即,一个被空格环绕的句点):

> (cons 1 2)

'(1 . 2)

> (cons "banana" "split")

'("banana" . "split")

因此,由cons产生的一个值并不总是一个列表。一般来说,cons的结果是一个 点对(pair)。更符合惯例的cons?函数名字是pair?,那我们从现在开始使用这个符合惯例的名字。

名字rest对非列表点对也意义不大;对first和rest更符合惯例的名字分别是car和cdr。(当然,符合惯例的名字也是没有意义的。请记住,“a”出现在“d”之前,并且cdr被声明为“could-er(可以)”。

Examples:

> (car (cons 1 2))

1

> (cdr (cons 1 2))

2

> (pair? empty)

#f

> (pair? (cons 1 2))

#t

> (pair? (list 1 2 3))

#t

Racket的点对数据类型和它对表的关系,连同打印的点符号和滑稽的名字car及cdr本质上是一个历史上的奇特事物。然而,点对深深地被连接进了Racket的文化、详述和实现上,因此它们在语言中得以存在下来。

在你犯一个错误时,你很可能会遇到一个非列表点对,比如不小心给cons把参数颠倒过来:

> (cons (list 2 3) 1)

'((2 3) . 1)

> (cons 1 (list 2 3))

'(1 2 3)

非列表点对有时被有意使用。例如,make-hash函数取得了一个点对的列表,其中每个点对的car是一个键同时cdr是一个任意值。

对新的Racket程序员唯一更困惑的情况莫过于非列表点对是对点对的打印习惯,其第二个元素是一个点对而不是一个列表:

> (cons 0 (cons 1 2))

'(0 1 . 2)

一般来说,打印一个点对的规则如下:除非该点紧接着是一个开括号,否则使用点表示法。在这种情况下,去掉点、开括号和匹配的闭括号。由此,'(0 . (1 . 2))变成'(0 1 . 2),'(1 . (2 . (3 . ())))变成'(1 2 3)。

2.4.1 用quote引用pair和symbol

一个列表在前面打印一个引号标记,但是如果一个列表的一个元素本身是一个列表,那么就不会为内部列表打印引号标记:

> (list (list 1) (list 2 3) (list 4))

'((1) (2 3) (4))

对于嵌套列表,尤其是quote表,你可以将列表作为一个表达式来写,基本上与列表打印的方式相同:

> (quote ("red" "green" "blue"))

'("red" "green" "blue")

> (quote ((1) (2 3) (4)))

'((1) (2 3) (4))

> (quote ())

'()

无论引用表是否由点括号消除规则规范,quote表都要包含点符号:

> (quote (1 . 2))

'(1 . 2)

> (quote (0 . (1 . 2)))

'(0 1 . 2)

当然,任何种类的列表都可以嵌套:

> (list (list 1 2 3) 5 (list "a" "b" "c"))

'((1 2 3) 5 ("a" "b" "c"))

> (quote ((1 2 3) 5 ("a" "b" "c")))

'((1 2 3) 5 ("a" "b" "c"))

如果用quote包裹标识,则得到看起来像一个标识的输出,但带有一个'前缀:

> (quote jane-doe)

'jane-doe

像一个引用标识那样打印的一个值是一个symbol(符号)。同样,括号输出不应该和表达式混淆,一个打印符号不应与一个标识混淆。特别是,除了符号和标识碰巧由相同的字母组成外,符号(quote map)与map标识或绑定到map的预定义函数无关。

的确,一个符号固有的值不过是它的字符内容。从这个意义上说,符号和字符串几乎是一样的东西,主要区别在于它们是如何打印的。函数symbol->string和string->symbol在它们之间转换。

Examples:

> map

#<procedure:map>

> (quote map)

'map

> (symbol? (quote map))

#t

> (symbol? map)

#f

> (procedure? map)

#t

> (string->symbol "map")

'map

> (symbol->string (quote map))

"map"

同样,对一个列表quote会自己自动作用于嵌套列表,在标识的一个括号序列上的quote会自己自动应用到标识上以创建一个符号列表:

> (car (quote (road map)))

'road

> (symbol? (car (quote (road map))))

#t

当一个符号在一个打印有'的列表中时,在符号上的这个'被省略了,因为'已经在做这项工作了:

> (quote (road map))

'(road map)

quote表对一个文字表达式,像一个数字或一个字符串这样的,没有影响:

> (quote 42)

42

> (quote "on the record")

"on the record"

2.4.2 使用'缩写quote

你可能已经猜到了,你可以通过仅放置一个'在一个表前面来缩写一个quote的使用:

> '(1 2 3)

'(1 2 3)

> 'road

'road

> '((1 2 3) road ("a" "b" "c"))

'((1 2 3) road ("a" "b" "c"))

在文档中,在一个表达式中的'和后面的表单一起被打印成绿色,因为这个组合是一个表达式,它是一个常量。在DrRacket,只有'被渲染成绿色。DrRacket更加精确校正,因为quote的意义可以根据一个表达式的上下文而变化。然而,在文档中,我们经常假定标准绑定是在范围内的,因此我们为了更清晰用绿色绘制引用表。

一个'以字面相当的方式扩展成一个quote表。你够明白如果你在一个有一个'的表前面放置一个'的这种情况:

> (car ''road)

'quote

> (car '(quote road))

'quote

'缩写在输出和输入中起作用。在打印输出时,REPL打印机识别符号'quote的两元素列表的第一个元素,在这种情况下,它使用’打印输出:

> (quote (quote road))

''road

> '(quote road)

''road

> ''road

''road

2.4.3 列表和Racket语法

现在你已经知道了关于点对和列表的真相,而且现在你已经明白了quote,你已经准备好理解我们一直在简化Racket真实语法的主要方法。

Racket的语法并不是直接在字符流中定义的。相反,语法是由两个层确定的:

打印和读取的规则是互相协调的。例如,一个列表用圆括号打印,读取一对圆括号生成一个列表。类似地,一个非列表点对用点表示法打印,同时在输入上的一个点有效地运行点标记规则从反向得到一个点对。

读取层给表达式的一个推论是你可以在不被引用的表的表达式中使用点标记:

> (+ 1 . (2))

3

这个操作因为(+ 1 . (2))只是编写(+ 1 2)的另一种方法。用这种点表示法编写应用程序表达式实际上从来不是一个好主意,它只是Racket语法定义方法的一个推论。

通常,.被仅只带一个括号序列的读取器允许,并且只有在序列的最后一个元素之前。然而,一对.也可以出现在一个括号序列的一个单个元素周围,只要这个元素不是第一个或最后一个。这样的一个点对触发一个阅读器转换,它将.之间的元素移动到列表的前面。这个转换使一种通用的中缀表示法成为可能:

> (1 . < . 2)

#t

> '(1 . < . 2)

'(< 1 2)

这两个点转换是非传统的,并且它与非列表点对的点记法基本上没有关系。Racket程序员保守地使用中缀标记——大多用于非对称二元操作符,如<和is-a?。

Racket编程指南——2 Racket概要相关推荐

  1. Racket编程指南——23 Racket和Scheme的方言

    23 Racket和Scheme的方言 我们使用"Racket"来指Lisp语言的一种特定方言,它基于Lisp家族的Scheme分支.尽管Racket与Scheme相似,但模块上的 ...

  2. Racket编程指南——21 运行和创建可执行文件

    21 运行和创建可执行文件 在开发程序时,很多Racket程序员使用DrRacket编程环境.要在没有开发环境的情况下运行程序,请使用racket(用于基于控制台的程序)或gracket(对于GUI程 ...

  3. Racket编程指南——4 表达式和定义

    4 表达式和定义 <Racket概要>这一章介绍了一些基本的Racket的句法表:定义.过程程序.条件表达式等等.本节提供这些形式的更详细信息,以及一些附加的基本表. 4.1 标记法 4. ...

  4. Racket编程指南——8 输入和输出

    8 输入和输出 一个Racket端口对应一个流的Unix概念(不要与racket/stream的流混淆). 一个Racket端口(port)代表一个数据源或数据池,诸如一个文件.一个终端.一个TCP连 ...

  5. Racket编程指南——20 并行

    20 并行 Racket提供两种形式的并行(parallelism):前程(futures)和现场(places).在提供多个处理器的平台上,并行可以提高程序的运行时性能. 关于Racket里顺序性能 ...

  6. Racket编程指南——13 类和对象

    13 类和对象 本章基于一篇论文[Flatt06]. 一个类(class)表达式表示一类值,就像一个lambda表达式一样: (class superclass-expr decl-or-expr . ...

  7. 《Java 7 并发编程指南》学习概要 (3)Semaphore, CountDownLatch, CyclicBarrier , Phaser, Exchanger...

    1.Semaphore  信号量 Semaphore(信号量)是一个控制访问多个共享资源的计数器. 当一个线程想要访问某个共享资源,首先,它必须获得semaphore.如果semaphore的内部计数 ...

  8. 多线程编程指南 part 2

    多线程编程指南 Sun Microsystems, Inc. 4150 Network Circle Santa Clara, CA95054 U.S.A. 文件号码819–7051–10 2006 ...

  9. Beej网络编程指南《一》

    Beej网络编程指南<一> 1简介 嘿!套接字编程让你失望了吗?这东西是不是有点太难从手册页上弄清楚了?你想做很酷的互联网编程,但是你没有时间费力地穿过一堆结构,试图弄清楚在连接()之前是 ...

  10. iPhone OS编程指南(一)

    原文地址:iPhone OS编程指南(一)作者:若水一叶 介绍 请注意:本文档之前命名为iPhone OS编程指南. iPhoneSDK为创建iPhone的本地应用程序提供必需的工具和资源.在用户的H ...

最新文章

  1. 《VMware Virtual SAN权威指南》一2.2 VSAN的要求
  2. Linux中的环境变量知多少?
  3. 微生物组助手——最易学的扩增子、宏基因组分析流程
  4. r230/r250/m200
  5. 前言 数组与指针的艺术
  6. Linux C++写日志
  7. HTML <!DOCTYPE> 标签
  8. hihoCoder #1639 图书馆
  9. elasticsearch 5.6.x单机环境构建(集成head插件和IK中文分词)
  10. Mysql 忘记管理员密码更改
  11. idea内置junit5_JUnit 5和Selenium –使用Selenium内置的`PageFactory`实现页面对象模式
  12. java编程一般类抽象类的定义,Java编程基础抽象类
  13. 再谈全局网HBase八大应用场景
  14. 刘强东凌晨发内部信:取消底薪不是为了降低工资
  15. 正确使用 realloc()
  16. 商业洞察力_正在进行的寻求洞察力和远见卓识
  17. 工程数学概率论统计简明教程第二版复习大纲
  18. Java 如何生成 UUID
  19. (ChibiOS )嵌入式操作系统 与 (OSAL)操作系统抽象层
  20. UDID 和 UUID 的问题

热门文章

  1. Divergence-Free Smoothed Particle Hydrodynamics
  2. ubuntu安装及使用笔记
  3. 图像scale与相机参数_摄像头参数测试指导分析解析
  4. three.js 05-08 之 TorusKnotGeometry 几何体
  5. 1005打印任务取消不了 hp_HP打印机打印时任务打印不了时无法取消
  6. 压缩解压缩文件zlib
  7. formidable词根词缀_托福词汇-重点词根词缀总结(二)
  8. [转]被历史歪曲得最多的皇帝--隋炀帝杨广简介
  9. 10.3注意力的评价函数
  10. stemming与lemmatization