2 Racket 要点

来源:互联网 发布:百度4字优化 编辑:程序博客网 时间:2024/06/16 21:19

本部分提供一个快速的介绍,关于本向导的后面剖分,作为Racket的背景知只, 有)) Ra
cket经验的可以直接跳到内建数据类型

2.1 Simple Values

Racket值包括数字,布尔,字符串和字节字符串.在DrRacket和文档的例子中(当你在读有颜包的文档),值表达式显示为 绿色.

1                3.141/2              6.02e+231+2i             9999999999999999999999

布尔值为:

#t = true#f = false

所有的非#f值,都被视为#t

字符串使用双引号包含,在字符串中反斜杠’\’为转义字符.例如一个 \” 在字符串中表示.除了未转义的双引号和返科杠,任何Unicode字符都可以出现在字符串常量中.

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

当一个常量在REPL环境中被计算,通常输出的形式同输入的一样.在某些特殊情况下,输出形式为输入的通常形式(input syntax). 在文档和DrRacket的REPL中,打印的结果为蓝色以区别于输入. 例如
1.0000
1.0
“Bugs \u0022Figaro\u0022 Bunny”
“Bugs \”Figaro\” Bunny”

2.2 Simple Definitions and Expressions

程序的模块写作:
#lang <langname> <topform>*

<topform>也是一个<definition>或 <expr>.

REPL也会计算.
在具体的层法中, 文本具有灰色背景,如 #lang代表文本的原义.空白必须出现在字面值和非终止符之间如 , 除了 (, ), [, ]. 注释以 ; 开头,并一直持续到该行的末尾, 注释被视为空白.

根据惯例, * 代表0次或多次重复在其前面的元素,+代表1次或多次重复前面的元素, {} 在重复当中代表一组序列.

2.2.1 Definitions

定义的形式:

(define <id> <expr> )

绑定<id><expr>的结果上.

(define (<id> <id>* ) <expr>+)

绑定第一个 <id>到一个函数(也叫过程)上,该函数有若干参数,即剩余的<id>. <expr>是函数体.当函数被调用时,返回值为最后一个<expr>.

例:

(define pie 3) ; 绑定 pie 的值为 3

(define (piece str) ; 定义 piece 是有一个参数的函数
(substring str 0 pie))
>pie
3
> (piece "key lime")
"key"

Under the hood, 函数定义与非函数定义一样, 在函数调用中,未必一定要出现函数名. 一个函数只是另一种类型的值,虽然打印出来的形式, 一定少了不少数字和字符串.
例如:

> piece<procedure:piece>>  substring#<procedure:substring>

函数定义的函数体中,可以包含多个表达式.正因如此,函数被调用是,只有最后一个表达式的值被返回.其它表达式只会因一些side-effect而被调用.如打印:
例:

(define (bake flavor)  (printf "pre-heating oven...\n")  (string-append flavor " pie"))> (nobake "green")"jello"

nobake函数内, 没有用括号括起 string-append , flavor , “jello”, 所以它们是三个分开的表达式,而不是一个函数调用.前两个被计算了,但是没有用到计算结果, 整个nobake 函数的返回值,是 “jello”.

2.2.2 An Aside on Indenting Code

换行和缩进对Racket解释器不重要.但是大多数Racket程序员使用标准的约定使代码更可读.例如:函数体通常在第一行下面并缩进,标识符通常写于左括号的右面(中间无空白),并且小括号结束于当前行.

DrRacket自动按照标准缩进,当你写入程序时,例如,如果输入 (define (greet name)并回车,DrRacket会自动在下一行缩进两个空格.如果你改变了代码,你可以选中它,并按tab键,DrRacket将会重新编进代码(不插入任何新行).其它编辑器如Emacs提供功能类似的Racket或Scheme模式.

重新格式化,不仅仅使代码更可读,并且给你一个额外的审视括号是否匹配的机会.例如:如果你在最后一个参数后面忘了),下一行将会从对齐于第一个参数,而不是从 define关键字下面开始.

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

// TODO

2.2.3 Identifiers

Racket的句法中标识符是十分自由的,除了以下的:

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

和由数字组成的,几乎所有的非空白序列都可以是<id>. 例如: substring, string-append, a+b,以级下面的例子:

+Hfuhruhurrinteger?pass/failjohn-jacob-jingleheimer-schmidta-b-c+1-2-3

2.2.4 Function Calls (Procedure Applications)

我们了经看过了很多函数调用,语法是:

( <id> <expr>*)

其中 <expr>的数量,取决于提供给函数<id>的参数的个数.
Racket预先定义了许多函数, 如 substring, string-append. 更多的例子见下面:
在文档的例子中,使用超链接,引用了手册中的预定义函数,可以点击链接获得更多细节.
> (string-append "rope" "twine" "yarn") ; append strings
"ropetwineyarn"
> (substring "corduroys" 0 4) ; extract a substring
"cord"
> (string-length "shoelace") ; get a string's length
8
> (string? "Ceci n'est pas une string.") ; recognize strings
#t
> (string? 1)
#f
> (sqrt 16) ; find a square root
4
> (sqrt -16)
0+4i
> (+ 1 2) ; add numbers
3
> (- 2 1) ; subtract numbers
1
> (< 2 1) ; compare numbers
#f
> (>= 2 1)
#t
> (number? "c'est une number") ; recognize numbers
#f
> (number? 1)
#t
> (equal? 6 "half dozen") ; compare anything
#f
> (equal? 6 6)
#t
> (equal? "half dozen" "half dozen")
#t

2.2.5 Conditionals with if, and, or, and cond

下一个简单类型表达式是 if 条件:

( if <expr> <expr> <expr>)

第一个 <expr>总是被计算,如果得到一个非#f的值,则第二个被计算,并作为整个表达式的返回值, 否则第三个被计算,并作为整个表达式的值.

// TODO

2.2.6 Function Calls, Again

2.2.7 Anonymous Functions with lambda

2.2.8 Local Binding with define, let, and let*

2.3 Lists, Iteration, and Recursion

Racket是一种 Lisp 方言, Lisp来源于 “LISt Processor.”的缩写.内建类型list是这种语言的重要的特性.
list函数,接收任何数量的值,并返回一个包含所有值的list:
> (list "red" "green" "blue")
'("red" "green" "blue")
> (list 1 2 3 4 5)
'(1 2 3 4 5)

可以看到一个list的结果输出为一个 ‘开始,并用括号包围所有值的形式. 这里容易造成混乱,因为 括号用于表达式(list "red" "green" "blue"),和输出结果("red" "green" "blue").除此之外, quote 括号作为结果打印为蓝色. 括号作为表达式打印为 棕色.

有许多操作列表的预定义函数, 下面是一些例子:

// TODO

2.3.1 Predefined List Loops

2.3.2 List Iteration from Scratch

2.3.3 Tail Recursion

2.3.4 Recursion versus Iteration

2.4 Pairs, Lists, and Racket Syntax

cons 函数实际上可以接收任何两个值, 第二个参数,不只是list. 当第二个参数不是empty并且不是它自己(由cons产生 ),结果会以一个特殊的方式输出: 两个值用括号括起来,并具中间有一个.

> (cons 1 2)
'(1 . 2)
> (cons "banana" "split")
'("banana" . "split")

所以, cons处理的结果并不总是list,通常,cons的结果是 pair. cons?更通俗的名字为pair?, 从现开始我们将使用 pair?.

rest对于非list的pair?也差点意思? firstrest更通俗的名字是 carcdr(cdr 读作 “could-er”)

例如:

> (car (cons 1 2))1> (cdr (cons 1 2))2> (pair? empty)#t> (pair? (list 1 2 3))#t

Racket的 pair数据类型及它与list的关系,与点号输出和 car/cdr, 是一个历史上的老古董. pairs 深植于Racket的文化,规范和具体实现之中, 因此他们得以在这本语言中存活. TODO

你可能常常在遭遇到非list数对时,犯错误.如 弄返了cons的两参数:

> (cons (list 2 3) 1)'((2 3) . 1)> (cons 1 (list 2 3))`(1 2 3)

非list数对儿,有时是故意使用的,如 make-hash 函数, 持有一个list,其中包含数对儿(pairs), 这时,每一个 pair的car都是key, cdr 是任意值.

唯一另人更困惑的事是: 非list数对儿是第二个元素为数对儿但不是list的数对儿的打印形式:

> (cons 0 (cons 1 2))'(0 1 . 2)

通常,打印数对儿的规则为:

  1. 使用点号. 除非.后面是开括号(左括号)
  2. 如果.后面是开括号(左括号),移除.,移除左括号及与之匹配的右括号
  3. '(0 . (1 . 2)) => '(0 1 . 2), '(1 . (2 . ( 3 . ()))) => (1 2 3).

2.4.1 Quoting Pairs and Symbols with quote

一个列表输出形式为,在前面加一个', 但是,如果一个列表中的一个元素本身也是一个列表,那么在内部的这个列表前面是不加'的:

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

对于嵌套的list,特别是 quote形式的, 本质上等同于 输出形式:

> (quote ("red" "green" "blue"))'("red" "green" "blue")> (quote ((1) (2 3) (4)))'((1) (2 3) (4))> (quote ())'()

‘quote’ 也可以与 ‘.’符号一起工作, 无论这个quote表达式是否被 .括号删除规则标准化(见 打印数对儿的规则)

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

当然, 任何种类的list 都可以被内嵌:

> (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

打印出来的,被'引用的标识符(identifier)是一个符号(symbol),以同样的方式,用括号括起来的不是一个表达式, 一个打印出来的符号不是一个标识符. 除了 这个符号和标识符是由同同的字母组成的以外,符号 (quote map)不会做任何与 map函数或预先定义的,绑定在map上面的函数有关的事情.

事实上, 符号的内存值不会多于它的字母内容. 从这方面来讲, 符号和字符串是一样的, 主要的不同是如何输出它们. 函数 symbol->string, string->symbol可以将二者相互转换.
例:

> 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"

同样, 对于list,'自动作用于内嵌的list, quote'在括起来的一系统标识符之前,自动作用于每一个标识符,并创建一个符号的列表:

> (car (quote (road map)))'road> (symbol? (car (quote (road map))))#t

当放在list内的符号(symbol),以'方式输出时,这个符号前面的'会删除, 因为'己经起作用了.

> (quote (road map))'(road map)

quote 对literal表达式没有影响,如数字和字符串:

> (quote 42)42> (quote "on the record")"on the record"

2.4.2 Abbreviating quote with ’

正如你所料,你可以把 '放在前面,作为quote的缩写:

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

'使用字面意义,展开一个quote形态. 如果你在一个quote form前面放一个':

> (car ''road)    ; (car (quote 'road))-> (car (quote (quote road))) -> (car (list 'quote 'road))'quote> (car '(quote road))'quote

'缩写的输出,使用和输入一样的工作方式. 当输出时,REPL认为 符号 'quote是 具有两个元素的列表的第一个元素,并使用 '进行输出:

> (quote (quote road))''road> '(quote road)''road> ''road''road

2.4.3 Lists and Racket Syntax

        >列表与Racket句法

现在我们知道了关于pair和list的真相,并且知道了 quote, 我们准备好了理解简化的racket句法的主要途径.

racket句法,不是直接使用字符流定义的,而是由两部分组成,

  • a reader layer, 它传化读入的字符流为 列表,符号,或其它常量,
  • an expander layer, 它处理 列表,符号及常量为表达式.

输入和输出的规则是相同的.例如,列表使用括号输出,读入序对儿被处理为列表.类似的, 非列表的序对儿pair使用点号输出,并且, 在输入点号时,实际上是使用 点号规则的逆规则来包含一个点号的.

> (+ 1 . (2)) ;=>(+ 1 2);点号后面有一个左括号.则去掉该点号和左括号以及和左括匹配的右括号3

上面的代码可以工作,是因为 (+ 1 . (2)) 就是(+ 1 2)的另一种形式. 实际上使用点号写程序不是一种好主意; 它只是一种Racket句法定义的结果.

通常, 在读入时,只有被括号括起的一个序列, .只能放在最后一个元素之前. 然而, 一对 .也可以出现在一个单独的元素两侧,只要这个元素不是第一个和最后一个. 这样一个序对 触发reader的转换规则:即移动这个元素到到列表的首位. The conversion 是一种 一般性的中缀 TODO:

> (1 . < . 2)#t> '( 1 . < . 2)'(< 1 2)> '(a b c . d . e)'(d a b c e)> '( a . b . c . c)read: illegal use of `.'

“双点转换”是非传统的,并且它实际上没有对非list数对儿做任何事. Racket 程序员对于使用中缀传换是很节省的. 通是他们使用 不对称的二元操作如 < is-a?.

原创粉丝点击