The Racket Guide阅读笔记,chapter 4.
4 Expressions and Definitions
4.1 Notation
有点没看懂。
4.2 Identifiers and Binding
跳过。
4.3 Function Calls (Procedure Applications)
4.3.1 Evaluation Order and Arity
函数调用从左到右求值。参数个数必须匹配。
4.3.2 Keyword Arguments
支持普通序参数以及关键字参数,比如:
(go "super.rkt" #:mode 'fast)
或者
(go #:mode 'fast "super.rkt")
4.3.3 The apply Function
apply函数可以将连对转化为函数的参数列表。
(define (avg lst)
(/ (apply + lst) (length lst)))
apply还可以在函数和参数列表之间插入额外的参数。
(define (anti-sum lst)
(apply - 0 lst))
apply函数还可以接受额外的关键字参数:
(apply go #:mode 'fast '("super.rkt"))
(apply go '("super.rkt") #:mode 'fast)
但是作为参数列表中的连对中不嫩包含关键字参数,这种情况下需要使用keyword-apply:
(keyword-apply go
'(#:mode) ; 这个参数,
'(fast) ; 和下面的参数并行
'("super.rkt"))
4.4 Functions (Procedures): lambda
4.4.1 Declaring a Rest Argument
略
4.4.2 Declaring Optional Arguments
lambda表达式可以声明可选参数:
(define greet
(lambda (given [surname "Smith"])
(string-append "Hello, " given " " surname)))
必须指定一个表达式作为可选参数的默认值:
(define greet
(lambda (given [surname (if (equal? given "John")
"Doe"
"Smith")])
(string-append "Hello, " given " " surname)))
4.4.3 Declaring Keyword Arguments
lambda表达式可以声明关键字参数:
(define greet
(lambda (given #:last surname)
(string-append "Hello, " given " " surname))
关键字参数也可以指定默认值:
(define greet
(lambda (#:hi [hi "Hello"] given #:last [surname "Smith"])
(string-append hi ", " given " " surname)))
对于剩余部分的参数中的关键字参数,需要通过make-keyword-procedure来创建(就像是keyword-apply至于apply):
(define (trace-wrap f)
(make-keyword-procedure
(lambda (kws kw-args . rest)
(printf "Called with ~s ~s ~s\n" kws kw-args rest)
(keyword-apply f kws kw-args rest))))
使用举例:
> ((trace-wrap greet) "John" #:hi "Howdy")
Called with (#:hi) ("Howdy") ("John")
"Howdy, John Smith"
4.4.4 Arity-Sensitive Functions: case-lambda
case-lambda可以根据输入参数的不同来对应不同的函数体。但是case-lambda不支持默认值或者关键字参数。
4.5 Definitions: define
略
4.5.1 Function Shorthand
define用于函数形式:
(define (id arg ...) body ...+)
当然,define形式的函数定义也支持默认值参数,以及关键字参数:
(define (greet first [surname "Smith"] #:hi [hi salutation])
(string-append hi ", " first " " surname))
当然也支持可变参数:
define (id arg ... . rest-id) body ...+)
4.5.2 Curried Function Shorthand
介绍curried函数:
(define make-add-suffix
(lambda (s2)
(lambda (s) (string-append s s2))))
4.5.3 Multiple Values and define-values
竟然有quotient/remainder这种东西。另外error
可以接受一个字符串作为错误消息。
4.5.4 Internal Definitions
define可以用在很多语法形式(如lambda,define-values,struct以及define-syntax)的内部,用作局部变量定义。
4.6 Local Binding
虽然define可以用来做局部绑定,但是更常用的是let, let*以及letrec。
4.6.1 Parallel Binding: let
略
4.6.2 Sequential Binding: let*
略
4.6.3 Recursive Binding: letrec
略
4.6.4 Named let
略
4.6.5 Multiple Values: let-values, let*-values, letrec-values
略
4.7 Conditionals
有很多函数用于分支预测,比如<
以及string?
。这些函数返回#t或者#f。实际上Racket把#f以外的值都当成真。
#f可以用来报告函数执行出错的情况,但是不要滥用这个操作。异常可能是更好的错误报告手段。
4.7.1 Simple Branching: if
略
4.7.2 Combining Tests: and and or
略
4.7.3 Chaining Tests: cond
略
4.8 Sequencing
Racket程序员习惯写副作用较少(也就是较少依赖外部状态)的函数。这样可以让函数易于测试且易于组合。但是和外部交互的时候,不可避免要涉及外部状态的改变。
4.8.1 Effects Before: begin
格式如(begin expr ...+)
。像lambda或者cond这种,默认就支持排排坐的,可以想象成有一个默认的begin在那里。
在顶层的,模块级别的,或者作为躯体作用咋内部定义的begin是特殊的,请他情况下,begin会被融入周遭的括号。
4.8.2 Effects After: begin0
begin0和begin类似,不过begin0返回第一个表达式的值。
4.8.3 Effects If…: when and unless
略
4.9 Assignment: set!
重新绑定一个id的值。(set!)
表达式的返回值是#<void>
。
4.9.1 Guidelines for Using Assignment
为了减少副作用,尽量少用set!
。下面是一些指导方针:
- 减少在不同函数执行期间使用共享的外部变量,尽量使用参数传递和返回值获取。
- 多个串行的赋值,不如多个重新绑定
(let ([tree 0])
(set! tree (list tree 1 tree))
(set! tree (list tree 2 tree))
(set! tree (list tree 3 tree))
tree)
上面改变tree所绑定的值,不如下面使用多个tree来给变量命名,每个tree在不同的作用范围
(let* ([tree 0]
[tree (list tree 1 tree)]
[tree (list tree 2 tree)]
[tree (list tree 3 tree)])
tree)
- 不要使用赋值来做累计,而是通过循环参数做累计
挫例子
(define (sum lst)
(let ([s 0])
(for-each (lambda (i) (set! s (+ i s)))
lst)
s))
好一点
(define (sum lst)
(apply + lst))
更好一点
(define (sum lst)
(for/fold ([s 0])
([i (in-list lst)])
(+ s i)))
- 只有对象内部需要维护状态是,使用
set!
才是合理的
(define next-number!
(let ([n 0])
(lambda ()
(set! n (add1 n))
n)))
对于某些数据类型,比如vector,使用vector-set!
貌似是合理且必须的。
4.9.2 Multiple Values: set!-values
(set!)
的多值版本。
4.10 Quoting: quote and ’
略
4.11 Quasiquoting: quasiquote and ‘
可能需要回顾下unquote-splicing啥意思。
4.12 Simple Dispatch: case
例子
(let ([v (random 6)])
(printf "~a\n" v)
(case v
[(0) 'zero]
[(1) 'one]
[(2) 'two]
[(3 4 5) 'many]))
4.13 Dynamic Binding: parameterize
parameterize看起是为scheme的文法作用域打开了一条缝,让其回到lisp的动态作用域。除了直接使用parameterize关键字,也可以使用 make-parameter
来创建动态参数。
- parameterize会自动重置变量的值,在异常发生的时候也可以。
- parameterize对尾递归友好。
- parameterize对线程友好。
(本篇完)