Marvin's Blog【程式人生】

Ability will never catch up with the demand for it

05 Mar 2021

The Racket Guide阅读笔记【六】

The Racket Guide阅读笔记,chapter 10.

10 Exceptions and Control

10.1 Exceptions

异常使用with-handler来捕获

(with-handlers ([exn:fail:contract:divide-by-zero?
                   (lambda (exn) +inf.0)])
    (/ 1 0))

在division by zero异常发生时将值替换成+inf.0

error可以用来直接抛出异常:

> (with-handlers ([exn:fail? (lambda (exn) 'air-bag)])
    (error "crash!"))
'air-bag

exn:fail:contractr:divide-by-zero以及exn:fail都是exn结构的子类型。

抛出异常的时候也可以不跑出exn相关的值:(raise 2)

使用(lambda (v) #t)可以捕获所有异常:

> (with-handlers ([(lambda (v) #t) (lambda (v) 'oops)])
    (car 17))
'oops

一般情况下,捕获exn:fail?即可处理常见场景:

> (with-handlers ([exn:fail? (lambda (v) 'oops)])
    (break-thread (current-thread)) ; simulate Ctl-C
    (car 17))

额外的工具

  • exn-message提取异常的错误信息
  • exn-continuation-marks提取异常的回溯信息

10.2 Prompts and Aborts

call-with-continuation-prompt在prompt之下对表达式进行计算。default-continuation-prompt-tag返回到prompt。abort-current-continuation逃逸到最近的prompt。

> (define (escape v)
    (abort-current-continuation
     (default-continuation-prompt-tag)
     (lambda () v)))
> (+ 1 (+ 1 (+ 1 (+ 1 (+ 1 (+ 1 (escape 0)))))))
0

异常的底层是通过prompt和abort来实现的。

10.3 Continuations

continuation(通量)是一个值,可表示求值的上下文。 call-with-composable-continuation可以捕捉当前的continuation。

> (define saved-k #f)
> (define (save-it!)
    (call-with-composable-continuation
     (lambda (k) ; k is the captured continuation
       (set! saved-k k)
       0)))
> (+ 1 (+ 1 (+ 1 (save-it!))))
3

> (saved-k 0)
3

> (saved-k 10)
13

> (saved-k (saved-k 0))
6

scheme传统上使用call-with-current-continuation或者call/cc来捕获continuation。但是此操作会在执行之前先abort然后再恢复。使用call-with-composable-continuation则不须由此顾虑。

11 Iterations and Comprehensions

for族语法形式可以支持在序列之上的迭代。List,vector,string,byte string,input port甚至哈希表这些都可以当作序列。in-range之流的构造函数可以为序列提供更多可能。

for语法格式如下:

(for ([id sequence-expr] ...)
  body ...+)

一个例子:

(for ([i '(1 2 3)])
    (display i))

for/list和for类似,不过会把结果累积成一个连对,例子:

> (for/list ([i '(1 2 3)])
    (* i i))
'(1 4 9)

11.1 Sequence Constructors

in-range产生一个数字序列:

> (for ([i (in-range 1 4 1/2)])
    (printf " ~a " i))
 1  3/2  2  5/2  3  7/2 

一个类似的函数时in-naturals,产生从0来时的自然数,无上限。

stop-before和stop-after可以使用一个判定诀来决定结束位置:

> (for ([i (stop-before "abc def"
                        char-whitespace?)])
    (display i))
abc

像in-list, in-vector以及in-string这些跟in-range相似,不过会要求特定的输入数据类型。

11.2 for and for*

for其实可以列举多个序列:

> (for ([i (in-range 1 4)]
        [chapter '("Intro" "Details" "Conclusion")])
    (printf "Chapter ~a. ~a\n" i chapter))
Chapter 1. Intro
Chapter 2. Details
Chapter 3. Conclusion

迭代的次数取决于最短的那个序列。

for*与for相同的地方时也可以指定多个序列,不同的地方时迭代时按照序列嵌套的。

使用#:when关键字可以在迭代的过程中进行筛选:

> (for* ([book '("Guide" "Reference")]
         [chapter '("Intro" "Details" "Conclusion")]
         #:when (not (equal? chapter "Details")))
    (printf "~a ~a\n" book chapter))

如果指定了多个#:when,那么每个#:when之间的内容时嵌套的而不是并行的,对于for循环也是如此。

#:unless关键字的效用和#:when在选择上正好相反。

11.3 for/list and for*/list

for/list和for类似,不过会以list的方式返回结果。 for*/list和for*就不那么类似了,它会返回平整后的结果,而不是嵌套的结果。

11.4 for/vector and for*/vector

11.5 for/and and for/or

for/and在迭代结果上执行and操作,当遇到#f的时候则停止迭代。

11.6 for/first and for/last

for/first返回有效迭代的第一个值。

for/last返回有效迭代的最后一个值。

11.7 for/fold and for*/fold

将迭代结果以某种形式合并在一起:

(for/fold ([accum-id init-expr] ...)
          (clause ...)
  body ...+)

例子:

> (for/fold ([len 0])
            ([chapter '("Intro" "Conclusion")])
    (+ len (string-length chapter)))
15

11.8 Multiple-Valued Sequences

迭代某些序列的时候需要处理多个值,比如把一个哈希表当作序列的时候。

> (for ([(k v) #hash(("apple" . 1) ("banana" . 3))])
    (printf "~a count: ~a\n" k v))
> (for*/list ([(k v) #hash(("apple" . 1) ("banana" . 3))]
              [(i) (in-range v)])
    k)
'("apple" "banana" "banana" "banana")

11.9 Breaking an Iteration

在for迭代中的子句以及体例中都可以插入#:break或者#:final。前者只要校验为真就立马退出,后者则会执行最后一次迭代后退出。

11.10 Iteration Performance

for为通用而涉及,所以可能需要花额外的判定在数据类型上,从而降低信息。一种改善的办法时主动提供信息从而减少for在判断上的消耗。具体查看文档。

(本篇完)