Scheme笔记
scheme笔记
几个概念 concept
identifier
标识符,可以看作是一个唯一的名字,可以用来引用变量,函数等等
variable
变量
atom
- a string of characters
- a string of digits
1 |
|
list
a collection of atoms enclosed by parentheses. example:
1 |
|
语法描述:
1 |
|
S-expression
- all lists
- all atoms
1 |
|
Naming Conventions
- 谓词后加? , 但是常用的数字比较=, <, >, <=, >=后面不需要?
- 类型测试, pair? , atom?
- 字符操作(char-xxx), 字符串操作(string-xxx), 向量操作(vector-xxx)
- 类型转换(type1->type2)
- 但函数有副作用时,应该以 ! 结尾, 比如
set!
core syntactic forms core syntactic form
top-level
define
forms 实际就是创建新的绑定,可以绑定list
,lambda procedure
这是几个示例代 码:1
2
3
4
5
6
7
8(define double-any
(lambda (f x)
(f x x)))
;;; identical
(define (double-any f x)
(f x x))
(define xyz '(x y z))constants BNF:
1
2
3
4<constant> -> <boolean>
| <number>
| <character>
| <string>variables
procedure applications
(procedure arg1 ... argn)
求值规则:- Find the value of
procedure
. - Find the value of
arg1
. - Find the value of
argn
. - Apply the value of
procedure
to the values ofarg1
…argn
.
- Find the value of
quote
expressions (‘)- quoting an
identifier
tells Scheme to treat the identifier as
asymbol
rather than as avariable
. 也就是说scheme解释器不会去当前的
环境中寻找与该标识符绑定的值, 而是直接将该标识符当作symbol也就是数据处理. - quoting a list tells scheme to treat the list as data, rather
than as a procedure application
- quoting an
lambda
expressions(lambda (x) (+ x x)) ==> #<procedure>
一般形式有这三种:
(lambda (var1 var2 ...) exp1 exp2 ...)
: var1 var2 …会依次赋值(lambda (var1 var2 . var) exp1 exp2)
: var1 var2会依次赋值, 余下的参数
会组成一个列表赋给var(lambda var exp1 exp2)
: 将所有的实参作为一个list赋给var, 注意var没有括号
if
expressions:(if test-expr then-expr else-expr)
只有test-expr
为#f
时才会运行else-expr
, 所以你一般要使用null?
eq?
这样的函数
来测试set!
expressions(Assignment): Assignments do not create new
bindings, as withlet
orlambda
, but rather change the values of
existing bindings.也就是说赋值不会像let, lambda那样产生新的绑定,而是会改变已存在绑
定的值,如果你给set!指定的符号不存在,它会报错(set!: cannot set undefined
variable)
其它的都是一些扩展,也就是可以通过define-syntax定义出来的,比如let,and,or,not等
等,只有以上的部分才需要解释器直接实现的
1 |
|
实际上define-syntax实际就是进行模式匹配,像cond,如果匹配那么就进行替换,比 如(and 1)就匹配((_ e)
e)所以就被替换为1, 就是and的占位符, 而 pat ...
代表 0个或者多个表达式, 比如(x v) ...
代表有0个或多个 (x v)
这样的表达式
let let
1 |
|
由于 scheme
对待中括号与对待小括号是一样的,所以为了可读性, let
一般可以用如下代 码:
1 |
|
form2主要是为了递归, 它使得在函数体中可以引用函数名, 比如下面的代码
1 |
|
let: 如上所见,实际就是一个扩展语法,a,b只在body中可见, 所以你在exp2中不能使 用a
let*: exp2中可以引用a, 也就是a可以用来定义b, 可以用嵌套的
let
来定义let*
1
2
3
4
5
6
7(define-syntax let*
(syntax-rules ()
[(_ () e1 e2 ...)
(let () e1 e2 ...)]
[(_ ((x1 v1) (x2 v2) ...) e1 e2 ...)
(let ((x1 v1))
(let* ((x2 v2) ...) e1 e2 ...))]))letrec: 主要用来解决定义递归函数时,函数名在函数体中不可见的问题,比如
1
2
3
4(let ([sum (lambda (lst)
(if (null? lst) 0
(+ (car lst) (sum (cdr lst)))))])
(sum '(1 2 3 4 5)))如果运行上面的代码,那么你会
sum undefined
的错误, 原因是sum
在后面的函数 体中不可见, 所以你使用(sum (cdr lst))
就会出错,当然你可以使用这种方法1
2
3
4(let ([sum (lambda (sum lst)
(if (null? lst) 0
(+ (car lst) (sum sum (cdr lst)))))])
(sum sum '(1 2 3 4 5)))但这种方法不够自然,而且比较丑陋,不符合scheme中定义递归函数的一般模式,所以 就引入了
letrec
.1
2
3
4
5
6(letrec ([sum (lambda (ls)
(if (null? ls)
0
(+ (car ls) (sum (cdr ls)))))])
(sum '(1 2 3 4 5)))
;; the result is 15letrec*: 和
letrec
类似, 只是后面的绑定可以引用前面已经绑定的变量let-values: 绑定多个变量 syntax:
- (let-values ((formals expr) …) body1 body2 …)
- (let*-values ((formals expr) …) body1 body2 …)
1
2
3
4
5(let-values ([(a b) (values 1 2)] [c (values 1 2 3)])
(list a b c)) ; the result is (1 2 (1 2 3))
(let*-values ([(a b) (values 1 2)] [(a b) (values b a)])
(list a b)) ; the result is (2 1)
control flow
if if
当要写多个表达式时应该加入 begin
, 注意 begin
会依次执行它所包含的表达式,并 返回最后一个表达式的值
1 |
|
如果 test-exp
为true, 依次执行 expression1
, expression2
那么返回expression2
的值, 之所以需要 begin
是由 if
的语法决定的, if
的语 法如下:
1 |
|
所以如果你在 true-exp
的位置放入多条表达式,那么这些表达式的第二条会 被当作 false-exp
,而且因为 if
后的表达式的条数超出 2
条而报错, 所以 你需要把多条表达式括起来,但是你不能直接加一个括号,比如
1 |
|
这样之所以不行是因为scheme会将 (true-exp1) 当作procedure求值,这显然不 对,
所以scheme使用begin(更准确的说begin是为了引入side effect), 也就变成 了:
1 |
|
这就是if使用begin的原因了, 注意在 let, lambda, define,cond的body中都不需
要begin,因为它们都没有if这种特殊的状况.
cond cond
内部相当于有个隐含的begin,所以可以直接写多个表达式
1 |
|
如果test-exp为true, 那么返回expression2的值 注意一个特殊的形式: =>
1 |
|
会将 test-exp的值传递给 =>后面的函数, =>后面必须是一个带一个参数的函数
when unless when unless
(when (test-exp) exp1 exp2) : test-exp为真就执行exp1, exp2
1
2
3(define-syntax when
(syntax-rules ()
[(_ e0 e1 e2 ...) (if e0 (begin e1 e2 ...))]))(unless (test-exp) exp1 exp2) : 只有当
test-exp
为#f时才会运行body中的 表达式
case case
类似于C语言的switch
1 |
|
do do
syntax: (do ((var init update) ...) (test result ...) expr ...)
循环(do ((var init update) …) (test res …) exp …) var的初始值是init,接
着每一次迭代都绑定到update, (test res..)如果为true,那么就终止循环
1 |
|
map
(map procedure list1 list2 …) 会返回一个 list
1 |
|
map
与 for-each
可以代替许多循环的工作, 而且逻辑上比循环更清晰.
for-each
和 map
类似但是不返回一个list作为结果, 也就是说 for-each
是用来产生side effect.
apply
(apply procedure obj … list)
(apply + '(4 5)) ===> 9
(apply min 5 1 3 '(6 8 3 2 5))
结果是1
multiple values
(values obj …) :返回多个值,注意它的返回值可以用
let-values
来绑定,不能用define
1
2(let-values ([(a b) (values 1 2)])
(+ a b))(call-with-values producer consumer): producer产生多个值, 然后将这些值传
递给consumer.注意producer必须可以不带参数的方式来调用1
2
3(call-with-values
(lambda () (values 'bond 'james))
(lambda (x y) (cons y x))) ; the result is (james . bond)
内置的函数
predication
- = : 只用来比较整数, 不要用来比较浮点数
- eq? : 可以类似的看作是指针比较, 即便是内容相同,但是如果是两个不同对象,它 就返回
#f
, 比如(eq? (cons 'a 'b) (cons 'a 'b))
虽然是同样的list, 可 是它在内存中的位置不同,所以为 #f.
有以下几种情况是相等的.#t, #f, 两个identifier的值如果都是#t或者#f,那么它们相等
相同的符号(symbol), 比如
1
2
3(define sym1 'hello)
(define sym2 'hello)
(eq? sym1 sym2)‘()
- eqv? : 和
eq?
很类似, 只是它在一些eq?
没有定义的地方也可以使用 - equal? : 只要内容相同,它就会返回 #t,可以认为它的检查比
eq?
宽松 - boolean? : 等价于
(lambda (x) (or (eq? x #t) (eq? x #f)))
- null? : 只作用于list, only
(null? '())
return#t
. - pair? :
- number? :
- complex? :
- rational? :
- real? :
- integer? :
- char? :
- string? :
- vector? :
- symbol? :
- procedure? :
- bytevector? :
- hashtable? :
list procedure
list
的语法定义:
1 |
|
cons: (cons ‘a ‘b) ==> (a . b),只能带两个参数
cons* : (cons* ‘a ‘b ‘c) ==> (a b . c) (cons* ‘a ‘b ‘(c)) ==>
(a b c)car: the firsts element of pair (only for non-empty list )
cdr: Only for non-empty list, the cdr of any non-empty list is also
a list.set-car! :有副作用, 会原地改变pair的值
set-cdr! :
car, caar, caaar…etc: 连续执行n次(a的次数)car (caar ‘((5)) ) ===> 5
cdr cddr cdddr …etc : 连续执行n次(d的个数)cdr
cadar : (car (cdr (car lst))) 记住按照顺序从左到右,最右边的先对list起作用
list: 创建一个list eg: (list 1 2 3 4) ===> ‘(1 2 3 4)
length: list的长度
append: 将一个list添加另一个list的后边,eg: (append ‘(1 2) ‘(3 4)) ===> ‘(1 2 3
reverse: 将list倒转
(list-ref list n): 第n个元素
(list-tail list n) : 倒数第n个元素
memq memv member memp 分别用eq? eqv? equal? 指定的procedure 来测试一个元
素是否属于list, 如果属于那么就返回包括该元素以及该元素后面的元素组成的 list,(memq 'a '(b c a d e)) --> (a d e)
1
2
3
4
5
6(define memq
(lambda (x ls)
(cond
((null? ls) #f)
((eq? (car ls) x) ls)
(else (memq x (cdr ls))))))remq (obj list) remv remove remp 删除list中的所有obj
assq assv assoc assp 可以看做是关联数组((key1 . val1) (key2 . val2) …)
1
2
3
4
5
6(define assq
(lambda (x ls)
(cond
((null? ls) #f)
((eq? (caar ls) x) (car ls))
(else (assq x (cdr ls))))))返回第一个匹配的pair
(filter procedure list): return a list of the elements of list for
which procedure returns true(partition procedure list): 返回两个list,第一个list包含所以使
procedure
为#f的元素,第二个包含所有使procedure
为#f
的元素,partition
的返回值可以使用let-values
来绑定或者使用call-with-values
来调用其它函数(find procedure list) : 返回第一个使procedure为#t的元素
(map f list1 list2 …)
(foldl f init list1 list2 …): racket 的内置版本有bug
(foldr f init list1 list2 …):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19(define foldl
(lambda (f x ls)
(cond
[(null? ls) x]
[else
(foldl f (f x (car ls)) (cdr ls))])))
;; (foldl op init '(1 2 3 4))
;; we can think foldl's behavior like this: (init op 1 op 2 op 3 op 4)
;; op has left associativity
(define foldr
(lambda (f x ls)
(cond
[(null? ls) x]
[else
(f (car ls) (foldr f x (cdr ls)))])))
;; (foldr op init '(1 2 3 4 5))
;; we can think foldl's behavior like this: (1 op 2 op 3 op 4 op init)
;; op has right associativity
number procedure
- zero? :
- positive? :
- negative? :
- even? :
- odd? : 奇数
- 数学函数: max, min, floor, ceiling(向+无穷取整),
truncate(向0取整),round(最接近的整数),
abs,gcd,expt(指数),三角系列函数(san,cos….)
char
字符使用 #\x
来表示,比如 a
就是 #\a
,
(char=? char1 char2 char3 …)
(char<? char1 char2 char3 …)
(char>? char1 char2 char3 …)
(char<=? char1 char2 char3 …)
(char>=? char1 char2 char3 …)
(char-ci=? char1 char2 char3 …) 大小写敏感
(char-ci<? char1 char2 char3 …)
(char-ci>? char1 char2 char3 …)
(char-ci<=? char1 char2 char3 …)
(char-ci>=? char1 char2 char3 …)
(char-upcase char) :大写,相对应的还有个char-downcase
(char->integer char)
(integer->char n)
string
双引号内的是字符串
- (string=? char1 char2 char3 …)
- (string<? char1 char2 char3 …)
- (string>? char1 char2 char3 …)
- (string<=? char1 char2 char3 …)
- (string>=? char1 char2 char3 …)
- (string-ci=? char1 char2 char3 …) 大小写敏感
- (string-ci<? char1 char2 char3 …)
- (string-ci>? char1 char2 char3 …)
- (string-ci<=? char1 char2 char3 …)
- (string-ci>=? char1 char2 char3 …)
- (string char …): 构建个包含指定的字符的字符串
- (make-string n), (make-string n char)
- (string-append string …)
- (substring string start end)
- (string-upcase string)
- (string->list): 把string转换成包含char的list
- (char->list lst)
vector procedure
list访问时需要遍历,不够高效, vector可以像数组一样O(1)时间内访问
- (vector obj …)
- make-vector: (make-vector 5 ‘a) ===> #(a a a a a)
- vector-length: vector的长度
- (vector-ref vec n)
- (vector-set! vec n obj)
- (vector-fill vec obj): 所有的元素都替换为obj
- list->vector :
- vector->list :
symbol procedure
每一个symbol在解释器内部都是指向同一个对象,所以用eq?来测试相同的symbol会返 回#t, 这也使得比较操作很高效
- symbol=? :symbol是否相等,也可以用eq?来比较
- symbol->string:
- string->symbol:
Hash Table
(make-eq-hashtable) : 使用eq?来比较两个key, 返回一个hashtable
(make-eq-hashtable size)
(make-eqv-hashtable) : 使用eqv?来比较两个key
(make-eqv-hashtable size)
(make-hashtable hash equiv?) : hash指定hash函数, equiv?指定比较两个key的 函数
(make-hashtable hash equiv? size)
(hashtable-set! hashtable key obj) :
(hashtable-ref hashtable key default) :
(hashtable-delete! hashtable key) :
(hashtable-size hashtable) : hashtable的大小
(hashtable-contains? hashtable key) :测试是否包含指定的key
macros
定义一个宏实际上就是把一个keyword与一个 macro transformer 绑定, 一个macro
transformer一般就是一个带有一个参数的函数, 而macro transformer的输入是一个原
始代码的syntax object, 输出则是包含转换后代码的 syntax object. syntax-rules
返回的就是一个macro transform.所以你可以抛开 syntax-rules
而这样写一个宏:
1 |
|
define-syntax
和define类似
1 |
|
let-syntax与letrec-syntax
1 |
|
syntax-rules
基本语法: (syntax-rules (literal ...) (pattern template) ...)
返回一个transformer, 实际就是一个 procedure
, 这个 procedure
接受一个 syntax
object,然后返回一个syntax object.
- literal: 一些关键字, 出现在pattern会原样匹配
- pattern: 用于匹配输入的表达式的模式
- template: 输出,记住template是原样输出,这是和syntax-case的最大区别.
- 下划线’_‘: 可以匹配任何结构,一般用来代表宏名
- … : 代表前面的部分重复0次或者多次
一个例子,这个例子实现一个类 if
的条件判断语句,他的形式是 (my-if cond then exp1 else exp2)
这看起来更可读性更好:
1 |
|
then, else
是关键字,所以它会原样的匹配输入
syntax object
(syntax template)
的缩写为 #'template
, syntax和quote很类似,只是它会将
template中的pattern variable替换掉, 并且会绑定上下文信息
- syntax: 创建一个字面的 syntax object,比如
(syntax '(+ 1 x))
, 简写#'(+ 1 x)
. - syntax->datum: 将一个syntax object转换为它原来的内容.
- identifier? : 测试一个syntax object 是不是标识符
- syntax-e : 只解包一层.
syntax-case
比 syntax-rules
更具一般性,而且更通用, 使用syntax-case的一般形式是:
1 |
|
解释一下上面的代码: 整个lambda定义的就是一个macro transformer, 它的唯一参数 x
实际就是一个包含了原始代码的syntax object. 通过syntax-case
来匹配x,然后返 回一个包含了转换后代码的syntax object
1 |
|
with-syntax
1 |
|
input and output
port
scheme的输入输出用到一个叫做port的对象,port也是first class object, port有很
多种类,比如文件,字符缓冲区, tcp网络连接, 管道等等
- 文件:
(open-output-file fname) : 返回一个port,用于将内容写入到文件
(open-input-file fname) : 返回一个port用于读取指定文件的内容
(close-output-port port)
(close-input-port port)
还有一种常用方式(更简单, 会自动关闭port):(call-with-input-file filename proc) : 打开filename并将得到的port传递
给proc, 并且调用完成时会关闭port(call-with-output-file filename proc) : 打开filename并将得到的port传递
给proc, 并且调用完成时会关闭port1
2
3
4
5
6
7
8(call-with-output-file "data"
#:exists 'truncate
(lambda (out)
(display "hello" out)))
(call-with-input-file "data"
(lambda (in)
(read-line in)))
- 字符缓冲区
- (open-output-string) :
- (open-input-string “xxx”):
default port
默认解释器会打开2个port, 一个输入,一个输出,分别会绑定到shell的输入输出,很 多实现还会打开一个error
port,用于IO的scheme procedure都可以带一个可选的参 数port, 如果不指定port,
那么就使用默认的port, 默认的port有这两个函数获得- (current-input-port)
- (current-output-port)
如果要改变默认的port,可以使用下面的两个函数:
- (with-input-from-file filename thunk) : 将默认的 input port重新绑定到文
件,这可以实现重定向 - (with-output-from-file filename thunk) : 将默认的 output port重新绑定到
文件,这可以实现重定向
输入输出
- 输入:
- read: 它会自动将读入的内容转换为scheme内置的数据结构,实际是一个递归下 降的parser
- (read-char), (read-char input-port): next character
- 输出:
- write
- display
- printf: 可以指定一个字符串来格式化,用的较多
- fprintf: 写入文件,多一个port参数
code example
1 |
|
递归思想
递归的思想要从逻辑上理解,在设计一个递归函数时,一开始就要明确该函数在逻辑上
的作用(不要一开始就陷入编码细节中),然后分清该函数在逻辑上与子问题的联系,并
以此为 依据来进行函数体的设计, 特别需要注意检查结束条件,比如加法(0), 乘法 (1), list(‘())就是一些结束条件.
递归函数的设计要点:
- 要明确检查终止条件(null? zero?)
- 要明确与子问题的关系,弄清分类的情况(cond)
continuation
scheme中获得当前continuation的构造是call/cc, let/cc
1 |
|
停机问题
1 |
|
上述代码中:eternity会永远运行,比如:
1 |
|
假设存在停机函数 will-stop?
, will-stop?
可以测试出一个函数是否会停机.那么:
- 假设
last-try
会停机, 那么(will-stop? last-try)
返回 #t, 因此也就会运行(eternity x)
, 前面说了, eternity会永远运行, 所以last-try
不会停机. - 假设
last-try
不停机, 那么(will-stop? last-try)
返回 #f, 因此也就不会运 行(eternity x)
, 那么last-try
很显然就会返回. 所以last-try
会停机
因此这就是个悖论.因此停机函数 will-stop?不存在
some example code(the little schemer)
racket
struct(新的数据类型) struct
语法形式: (struct struct-id (field-id ...))
- struct-id : 是一个constructor, 可以用来构建一个该数据类型的实例
- struct-id? :一个predication,测试是否是该数据类型的实例
- struct-id-field-id: 从实例中获取
field-id
属性的值
下面来看个例子:
1 |
|
复制更新
根据已有对象更新其中的特定域然后返回新对象
语法形式: (struct-copy struct-id struct-expr [field-id expr] ...)
1 |
|
subtypes(类似于继承) subtypes
语法形式: (struct struct-id super-id (field-id ...))
1 |
|
那么pos3d就有 (x y z)
三个属性
match(模式匹配) match
正则表达式只能用来匹配字符串,而 match
可以用来匹配任何的 scheme value
, 它的语法形式如下:
1 |
|
将 target-expr
与 pattern匹配, 如果匹配成功就执行后面的expr, 对pattern的 语法要做以下说明:
… 或 _ : 代表0次或者多次
..k 或 _k : 代表至少
k
此 下面是一个将let转换为等价的lambda形式的例子1
2
3
4
5
6
7(match '(let ([a 1]
[b 2])
(set! a 11)
(- a b))
[`(let ([,var* ,val*] ...) ,body* ...)
`((lambda (,@var*)
,@body*) ,@val*)])注意因为…的作用,上面的var*, val*, body*都是列表, 所以在后面需要使用 ,@来 分解.
literal: 字面值直接用
equal?
测试是否相等1
2
3
4
5
6
7(match 2
[1 'one]
[2 'two]
[3 'three])
(match #f
[#t 'yes]
[#f 'no])(list lvp …): 会绑定对应的标识符,注意几个特殊符号,vector和list类似
1
2
3
4
5
6
7
8
9(match '(1 + 2)
[(list a '+ b) (+ a b)]) ;return 3
(match '(1 2 3)
[(list 1 a ...) a]) ;return '(2 3)
(match '(1 2 3 4)
[(list 1 a ..3) a]
[_ 'else]) ;return '(2 3 4)上例中
a b
就被绑定为 1, 2(struct-id pat …)或者(struct struct-id (pat …)):匹配一个实例,并且绑定 一些变量
1
2
3(define-struct tree (val left right))
(match (make-tree 0 (make-tree 1 #f #f) #f)
[(tree a (tree b _ _) _) (list a b)]) ;'(0 1)(struct struct-id _) :匹配任何 struct-id的实例
(? expr pat …): expr是一个predication, 只有它返回true的时候,才会匹配后 面的pat
1
2(match '(1 3 5)
[(list (? odd?) ...) 'yes])它的工作原理是这样,以下面的例子为例:
1
2(match '((1 2) 3 6)
[`(,(? pred? `(,a1 ,a2)) ,b ,c) `(,a1 ,b ,c)])如果上面的例子匹配, 那么b, c应该分别绑定3, 6, 那么前面的
(? pred? `(,a1 ,a2))
应该匹配(1 2), 所以先将(1 2) 传递给pred?, 如果pred? 返回#t, 那么接 着用(1 2)
去匹配后面的`(,a1 ,a2). 也就是说pred? 只会检查(? pred? …) 匹配 的那一部分.(quasiquote qp): unqote或unquote-splicing的部分会绑定为变量,其它部分会原样匹配
1
2(match '(1 + 2)
[`(,a + ,b) (+ a b)])a b都是unquote指定的部分,所以绑定为变量, 其它部分比如 + 就原样匹配, 可以和 (? expr pat …)结合使用:
1
2
3(match '(+ a "hello")
[`(+ a ,(? number? x)) x]
[`(+ a ,(? string? x)) x])关于quasiquote的一些说明:
quasiquote(`): 和quote(‘)类似, 只是表达式中的unquote会求值在返回,如果没
有unquote,那么它的行为就和quote
一样,来看几个例子:(cons a b)
等价于`(,a ,b)
unquote(,): 指定的部分会先求职,在插入list中
1
2
3(quasiquote (1 2 (unquote (+ 1 2)) (unquote (- 5 1))))
;;; equivalent
`(1 2 ,(+ 1 2) ,(+ 5 1)) ;'(1 2 3 6)unquote-splicing(,@): 和unquote的行为类似, 只是它所指定的表达式求值后必
须返回list,这个list中的元素会拆开然后插入原list中1
`(1 2 ,@(list (+ 1 2) (2 2)) 5) ;'(1 2 3 4 5)
module
module的基本语法是:
1 |
|
例子:
1 |
|
- name-id: module的名字,上例是
cake
- initial-module-path: 初始化要导入的module, 上例是
racket
- provide : 可选的,也就是规定哪些东西是可以导出的,上例中
print-cake
会导 出,但是show
是模块私有,
如果不提供provide
那么所有的属性都是私有的, 所以一个模块必须指定provide
才对导入者有意义
声明一个模块并不会直接对模块的 body
部分求值,只有当使用 require
明确导 入该模块时才会求值
submodule
一个文件只能包含一个顶层模块,这个模块可以通过 module
指定,也可以通 过 lang lang-name
来间接的指定,
那么在这个顶层模块中又可以定义子模块,比 如如下代码:
1 |
|
上面的代码(假设文件名是park.rtk) #lang racket
指定了顶层模块, 而 zoo
是 一个子模块,
在顶层模块中可以直接通过 (require 'zoo)
来包含子模块, 如果是 在该文件以外,
你需要导入该文件的模块, 使用 (require "park.rtk")
只会导入 顶层模块, 使用submode语法比如(require (submod "park.rtk" zoo))
就可以导 入子模块了
module* :module*:
1
2(module* name-id initial-module-path-or-#f
decl ...)使用
module
声明的子模块可以可以被父模块导入, 但是子模块不能导入父模块, 而恰恰相反,使用module*
声明的模块可以导入父模块, 可是父模块不能导入该子 模块, 如果指定#f
作为默认导入模块,
那么父模块中的所有绑定在子模块中都可 见.1
2
3
4
5
6
7
8
9
10
11
12
13
14#lang racket
(define (print-cake n)
(show " ~a " n #\.)
(show " .-~a-. " n #\|)
(show " | ~a | " n #\space)
(show "---~a---" n #\-))
(define (show fmt n ch)
(printf fmt (make-string n ch))
(newline))
(module* main #f
(print-cake 10))在上面的
main
这个submodule中, 顶层模块的所有绑定都可见, 注意当一个
submodule的名字为main时,有一个特殊的地方,也就是说当前的文件作为执行文件时
(racket file-name.rtk),即便你没有使用(require 'main)
语句, 这个子模块仍
然会运行,和python的=ifmain= 很类似,所以这个main模块可以写一些本模块的测试 代码module+ :module+:
1
2(module+ name-id
decl ...)等价于
1
2(module* name-id #f
decl ...)module+一般用来写
test
模块, 多个test模块会合并为一个test模块, 使用raco test
filename.rkt来运行测试代码
lang
racket的源文件一般需要使用 #lang lang-name
这样的方式来指定语言,这个实际是一 个module的简写方式,比如#lang racket
等价于:
1 |
|
所以 #lang racket
的意思就是定义一个module, 该模块的名字一般继承自文件名, 然后将racket作为初始模块导入.
require
如果是文件那么应该使用这样的语法:
1
2
3(require "aa.rkt")
(require "../aa.rkt")
(require "../subdirectory/aa.rkt")以当前文件的路径为当前路径,使用和shell类似的路径表达方式来确定需要导入的模 块的名字,记住要带扩展名
当前文件的子模块:
(require 'name)
标准模块:
(require racket)
子模块:
(require (submod "aa.rkt" submod-name))
provide
- 导出所有:
(provide (all-defined-out))
- 只导出通过require引入的绑定:
(provide (all-from-out))
- 导出除指定的外所有的绑定:
(provide (except-out name ...))
- 重命名:
(provide (rename-out [orgn-id export-id]))
some example:
1 |
|
typed racket
sample program
1 |
|
Types
Basic Types
- Number
- Char
- String
- Boolean
Function Type
(Number -> Number) (String String -> Number)
Union Type
当一个类型有几种变种时, 应该使用Union Type
1 |
|
上面的Tree就包含两种类型, Node与Leaf
Recursive Type
1 |
|
Subtyping
任何类型都是Any的子类型.
Polymorphism
1 |
|
注意上面的 All
.
Footnotes
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!