Marvin's Blog【程式人生】

Ability will never catch up with the demand for it

26 Feb 2021

The Racket Guide阅读笔记【三】

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对线程友好。

(本篇完)

Categories