为什么要学
吃饱了撑的
基本语法
racket 的语法和 lisp 有点像(我没学过 lisp ),都是由一堆括号组成的,可以看一下例子
1
2
|
(+ 1 1) ; means 1 + 1
(* 1 2) ; means 1 * 2
|
;
表示单行注释的开头。
racket 有一个非常宽松的关键字系统,除了
(
)
[
]
{
}
"
,
'
``` ;
#
|
\
以外和一些数字相关的字符外。
基本类型
racket 支持 boolean,number,string,bytestring。
number 的范围就广了…
举个例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
> 1
1
> 1/2
1/2
> 1+2i
1+2i
> 3.14
3.14
> 1e5
100000.0
> "QWERASD"
"QWERASD"
> "Bugs \u0022Figaro\u0022 Bunny"
"Bugs \"Figaro\" Bunny"
>
|
racket 中的 true 为 #t
false 为 #f
define 和简单的表达式,函数
define 可以定义一个常量,一个函数等
1
2
3
4
5
|
(define pi 3.14)
(define (square x)
(* x x)
)
(square pi)
|
这里我们就定义了一个 pi
当然标准库里也有 pi
,我们这样就相当于把库里的 pi
替换了,我们甚至可以 (define define ...)
这样可以把 define
搞没~
回到正题,我们定义了一个 pi
,然后定义了一个函数 square
,这个函数有一个参数,形参是 x
,函数体是 (* x x)
。
最后,我们执行了 square
这个函数,参数是之前定义的 pi
。
这段代码换成命令式大概长这样:
1
2
3
4
5
|
double pi = 3.14;
void square(double x) {
return x * x;
}
square(pi);
|
当然 racket 里的 x
可能是 “泛型” 。
racket 中还有一些看起来有意思的函数
1
2
3
4
|
(eq? 1 3) ; #f
(eq? 1 1) ; #t
(>= 1 1) ; #t
(> 1 1) ; #f
|
再举一个有意思的例子,我们写一个把输入参数翻倍的函数,是字符串就重复自身,是数字就乘2。
一般我们会这么写:
1
2
3
4
5
6
7
|
(define (double x)
(if
(string? x)
(string-append x x)
(* 2 x)
)
)
|
其实,你甚至能返回 “操作符”
1
2
3
4
5
6
|
(define (double x)
(
(if (string? x) string-append *) ; 返回了操作符,对后面的参数进行操作
x x
)
)
|
set!
set!
就是对之前定义过的变量再次赋值
1
2
3
4
5
6
7
8
9
10
|
(define a 1)
(define (add) (set! a (+ a 1)))
(add)
a ; 2
(add)
a ; 3
(add)
a ; 4
(add)
a ; 5
|
set!-values
emmmm不知道怎么形容,写个例子吧
1
2
3
4
5
|
(define (swap a b) (set!-values (a b) (values b a))
(display a)
(newline)
(display b))
(swap 1 2) ; 2 1
|
lambda
看一下就懂啦
1
2
3
4
5
6
7
8
9
10
11
12
|
(lambda (x y z) (+ x y z)) ; 简单的 3个数相加,这是没调用的情况,输出 #<procedure>
((lambda (x y z) (+ x y z)) 1 2 3) ; 6
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (double x)
(if
(string? x)
(string-append x x)
(* 2 x)
)
)
(double ((lambda (x) (+ x x)) 6)
)
|
可选的参数列表
1
2
3
4
5
6
7
8
9
10
11
|
(define opt (lambda (x [arg1 1] [arg2 2])
(+ x arg1 arg2)
))
(opt 1)
(opt 1 2)
(opt 1 2 3)(define opt (lambda (x [arg1 1] [arg2 2])
(+ x arg1 arg2)
))
(opt 1) ; 4
(opt 1 2) ; 5
(opt 1 2 3) ; 6
|
关键字参数
也就是给参数指定个“名字”,使用的时候无关参数顺序
1
2
3
4
5
6
7
8
9
|
(define opt (lambda (x #:arg1 [arg1 1] #:arg2 [arg2 2])
(+ x arg1 arg2)
))
(opt 1)
(opt #:arg1 1 2)
(opt 1 #:arg2 2 #:arg1 3)
(opt 1) ; 4
(opt 1 2) ; 5
(opt 1 2 3) ; 6
|
if
现在来看看 if
该如何写
首先, if
是个有 3 个参数的 函数
(我随便叫的),所以我们应该这么写
1
2
3
|
(if (> 1 2)
"1 > 2"
"1 <= 2")
|
显然的,第一个参数是条件,第二个参数是 true 的时候的走向,第三个参数是 false 的时候的走向。
由于 racket 中 没有 else if
所以我们只能嵌套的写 if。
1
2
3
4
5
6
7
8
|
(define (what x)
(if (> x 90) ; x > 90 ?
"NB" ; x > 90!
(if (> x 60) ; x <= 90
"PASS" ; x > 60!
"NOT PASS") ; x <= 60!
)
)
|
when & unless
when
就是一个没有 else 的 if
1
|
(when (< 1 0) (display "< 1 0")) ; nothing to to
|
unless
是 when
的相反啦
1
|
(unless (< 1 0) (display "< 1 0")) ; < 1 0
|
提到了 if 必然会有逻辑与和逻辑或。在 racket 中,and
和 or
也很有意思:
1
2
3
4
5
6
7
8
|
(and #t #t) ; #t
(and #t #f) ; #f
(or #t #t) ; #t
(or #t #f) ; #f
(and #t #t 6 7) ; 7
(and #t #f 6 7) ; #f
(or #f #t 6 7) ; #t
(or #f #f 6 7) ; 6
|
可以这么理解:
and
会寻找 #f
,进行返回并停止求值,如果找不到,那就返回最后一个值。
or
会寻找一个不为 #f
的值,然后返回并停止求值。
cond
上面的 if 写起来又臭又长,所以我们可以换种方法写
cond 给了一种非常方便的方式,可以写无限多的条件,如果第一个 condition expr 返回为 true,程序就会终止向下求值。
1
2
3
4
5
6
7
8
9
|
(define (gt x)
(cond
[(>= x 90) ">= 90"]
[(>= x 80) ">= 80"]
[(>= x 70) ">= 70"]
[(>= x 60) ">= 60"]
[else "< 60"]
)
)
|
case
看例子吧,都是和分支跳转相关的,值得注意的是,最后一条语句可以使用 else
1
2
3
4
5
6
7
8
9
|
> (let ([v (random 6)])
(printf "~a\n" v)
(case v
[(0) 'zero]
[(1) 'one]
[(2) 'two]
[(3 4 5) 'many]))
5
'many
|
let 声明
define
在函数中声明终归不太方便,我们可以使用 let
声明变量,看看官方的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
(let ([x (random 4)]
[o (random 4)])
(cond
[(> x o) "X wins"]
[(> o x) "O wins"]
[else "cat's game"]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
> (let ([me "Tarzan"]
[you "Jane"])
(let ([me you]
[you me])
(list me you)))
'("Jane" "Tarzan")
|
(let ( [id (expr)]* ) expr+ )
let*
let
无法使用之前声明的值,比如我想这么写
1
2
3
4
5
6
|
(let ([a 6]
[b 7]
[c (+ a b)]
)
(display c)
)
|
是行不通的,c = a + b
我们需要使用 let*
1
2
3
4
5
6
|
(let* ([a 6]
[b 7]
[c (+ a b)]
)
(display c)
)
|
有意思的多次声明,看官方的例子:
1
2
3
4
5
6
7
|
(define f ; f 是一个 lambda 函数
(lambda (append) ; append 是参数
(define cons (append "ugly" "confusing")) ; cons 被赋值为 '("ugly" "confusing")
(let ([append 'this-was]) ; 让 append 赋值为 'this-was
(list append cons)))) ; 返回一个 '(append cons)
> (f list)
'(this-was ("ugly" "confusing"))
|
let-values let*-values
要是我们要声明一大堆变量的话,写一堆let或者方括号们显然不是很方便,或者说一个函数返回两个值,我们想对这两个值进行相应的变量绑定,比如说 (quotient/remainder 14 3)
就会返回两个值。
1
2
|
(let-values ([(a b c d) (values 1 2 3 4)])
(+ a b c d))
|
values
用于返回多个值
1
2
3
4
|
> (values 1 2 3)
1
2
3
|
pair
pair
是一对值,他只有两个参数,或者说,他是一个特殊的list。
我们可以使用 cdr
获取 pair 的第一个元素,用 cdr
获取 pair 的最后一个元素
list
如何定义一个list:
1
2
3
4
|
(list 1 "2" 3.14)
'(1 2 3.14)
(quote (1 2 3 4))
(cons 1 (cons 2 (cons 3 empty))) ; '(1 2 3) cons 是 `construct` 的缩写
|
一些 list 相关的函数,看官方的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
> (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
> (first '(1 2 3)) ; 取头节点 car 也是ok的
1
> (rest '(1 2 3)) ; 除了头节点剩下的部分 cdr 也是ok的
'(2 3)
|
我们可以用 length
获取列表长度,那么如何自己写一个 my_len
呢
1
2
3
4
5
6
7
|
(define (my_len lst)
(cond
[(empty? lst) 0]
[else (+ 1 (my_len(rest lst)))]
)
)
(my_len '(1 2 3)) ; 3
|
这么写效率可能不是很ok,我们可以看到其实递归的过程是这样的:
1
2
3
4
5
6
7
8
|
(my_len (list "a" "b" "c"))
= (+ 1 (my_len (list "b" "c")))
= (+ 1 (+ 1 (my_len (list "c"))))
= (+ 1 (+ 1 (+ 1 (my_len (list)))))
= (+ 1 (+ 1 (+ 1 0)))
= (+ 1 (+ 1 1))
= (+ 1 2)
= 3
|
我们可以写一个尾递归的版本:
1
2
3
4
5
6
|
(define (my_len lst)
(define (it lst len)
(cond
[(empty? lst) len]
[else (it (rest lst) (+ len 1))]))
(it lst 0))
|
可以看出函数的执行过程是这样的:
1
2
3
4
5
6
|
(my_len (list "a" "b" "c"))
= (it (list "a" "b" "c") 0)
= (it (list "b" "c") 1)
= (it (list "c") 2)
= (it (list) 3)
3
|
apply
看官方用例吧
1
2
3
4
5
6
7
8
|
(define (avg lst)
(/ (apply + lst) (length lst)))
> (avg '(1 2 3))
2
> (avg '(1 2))
3/2
> (avg '(1 2 3 4))
5/2
|
vector
vector
是一个定长的数组,和 list 不同的是,他对元素有一个常数时间的查询和更新。
和 list 相同的是,他的元素也可以是不同类型的。
如何声明一个 vector
:
1
2
|
#(1 2 3)
#(1 "2" 3.14)
|
对 vector 操作:
1
|
(vector-ref #(1 "2" 3.14) 2) ; 3.14
|
vector 可以和 list 进行相互的转换,可以看官方的例子:
1
2
3
|
> (list->vector (map string-titlecase
(vector->list #("three" "blind" "mice"))))
'#("Three" "Blind" "Mice")
|
hashmap / hashtable
racket 中哈希表 key 的比较取决于创建哈希表使用的是哪个函数。
在 racket 中,任何的 racket值 都能作为 key
equal?
⇒ make-hash
eqv?
⇒ make-hasheqv
eq?
⇒ make-hasheq
1
2
3
4
5
6
7
8
9
|
(define hs (make-hash))
(hash-set! hs "6" 7)
(hash-set! hs '666 777)
(hash-set! hs '(789,654) 1)
(hash-set! hs 7 6)
(hash-ref hs 7) ; 6
(hash-set! hs 7 8)
(hash-ref hs 7) ; 8
|
为了方便,我们还可以这样创建哈希表
1
2
3
|
(define hs #hash((1 . 2) (2 . 3)))
; 当然你也可以用 #hasheq #hasheqv 创建相应的 hashmap 类型
(hash-ref hs 1) ; 2
|
hashmap 里面的键值对是 pair
所以我们做初始化的时候可以使用 pair
的形式初始化。
可变 hashtable 可以选择创建弱保留键的 hashtable,看官方的例子:
1
2
3
4
5
|
> (define ht (make-weak-hasheq))
> (hash-set! ht (gensym) "can you see me?")
> (collect-garbage)
> (hash-count ht)
0
|
boxes
这玩意相当于只有一个元素的vector。
看官方例子吧:
1
2
3
4
5
6
7
8
|
> (define b (box "apple"))
> b
'#&"apple"
> (unbox b)
"apple"
> (set-box! b '(banana boat))
> b
'#&(banana boat)
|
map
map 函数就是对个列表内的元素干一遍活,然后返回一个修改后的列表。
1
|
(map sqrt '(1 4 9 16 25 36)) ; '(1 2 3 4 5 6)
|
此外,还提供了 andmap
ormap
函数,返回一个 bool值,看例子就明白了
1
2
|
(andmap string? (list "a" "b" "c")) ; #t
(ormap string? (list "a" "b" 6)) ; #t
|
当然啦
map
andmap
ormap
的第二个参数(也就是那个函数)都是可以接受多个列表作为参数的,但是这些列表都必须有相同的长度
(map/ormap/andmap (function name) (arguments...))
看个例子就懂了
1
|
(map (lambda (a b) (+ a b)) '(1 2 3) '(4 5 6)) ; '(5 7 9)
|
filter
字如其名,就是过滤掉列表中一些不想要的东西,返回过滤后的结果。
1
|
(filter number? '(1 2 3 4 "666" 3.14 '(1 2 3))) ; '(1 2 3 4 3.14)
|
foldl
foldl
就是给一个初始值,然后给一个列表,他会把列表里的东西用函数和初始值“混合”在一起,下面是一个对列表里的数进行 乘2求和,结果加1的例子:
1
2
3
4
|
(foldl (lambda(elem n) (+ n (* 2 elem))) ; n + 2 * elem
1 ; 初始值为 1
'(0 1 2 3 4)) ; elements
; 21
|
quote 和 '
之前在声明列表的时候用过这个玩意 quote
'
。
'
是 quote
的一个语法糖罢了。
如果你使用 '
后接一个 identifier,那么这个 identifier 就会变成一个所谓的 “符号”。
看官方的例子吧
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
> 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"
|
众所周知,racket使用前缀表达式进行计算,如果我们想用中缀怎么做呢
1
2
3
|
(1 . + . 2) ; (+ 1 2)
(1 . < . 2) ; (< 1 2)
'(1 . + . 2) ; '(+ 1 2) 中缀转前缀
|