Marvin's Blog【程式人生】

Ability will never catch up with the demand for it

03 Mar 2021

The Racket Guide阅读笔记【五】

The Racket Guide 阅读笔记,chapter 7.

7 Contracts

7.1 Contracts and Boundaries

合约(contract)是一个模块为其自身提供的使用声明。

#lang racket
 
(provide (contract-out [amount positive?]))

上述合约声明确保amount永远为正。

对于#lang racket/base,需要显示(require racket/contract),如同:

#lang racket/base
(require racket/contract) ; now we can write contracts
 
(provide (contract-out [amount positive?]))

7.1.1 Contract Violations

前面例子中的positive?只适用于数字,如果amount不是数字,那么positive?就会出错。一个解决办法是使用and/c确保它既是数字又为正值:

(provide (contract-out [amount (and/c number? positive?)]))

7.1.2 Experimenting with Contracts and Modules

racket支持submodule,例如:

#lang racket
 
(module+ server
  (provide (contract-out [amount (and/c number? positive?)]))
  (define amount 150))
 
(module+ main
  (require (submod ".." server))
  (+ amount 10))

7.1.3 Experimenting with Nested Contract Boundaries

可以在更细颗粒度上使用合约,这涉及到define/contract

#lang racket
 
(define/contract amount
  (and/c number? positive?)
  150)
 
(+ amount 10)

7.2 Simple Contracts on Functions

合约也可以应用于函数:

(provide (contract-out
          [deposit (-> number? any)]
          [balance (-> number?)]))

->后面首先指定输入值,然后是返回值约束。

7.2.1 Styles of ->

(-> number? any)也可以写成(number? . -> . any)

7.2.2 Using define/contract and ->

你也可以在函数上使用define/contract

(define/contract (deposit amount)
  (-> number? any)
  ; implementation goes here
  ....)

但是这会导致每次函数调用的时候都进行合约检查,可能会影响性能。然后合约检查对于此函数在模块内部的调用也会进行。

7.2.3 any and any/c

上面deposit函数中使用的any合约表示deposit可以返回任意值,一个或者多个值。any只可以用于约束返回值,上面的例子中,也可以将any替换成具体的void?

除了any之外,还可以使用any/c,表示只会返回单个任意值。

7.2.4 Rolling Your Own Contracts

可以自定义合约:

(define (amount? a)
  (and (number? a) (integer? a) (exact? a) (>= a 0)))

也可以通过and/c以及or/c来定义:

(define amount/c
  (and/c number? integer? exact? (or/c positive? zero?)))

另一个例子:

(provide (contract-out
          ; convert a random number to a string
          [format-number (-> number? string?)]
 
          ; convert an amount into a string with a decimal
          ; point, as in an amount of US currency
          [format-nat (-> natural-number/c
                          (and/c string? has-decimal?))]))

也可以使用regexp来作为合约

#lang racket
 
(provide
 (contract-out
  ....
  ; convert an  amount (natural number) of cents
  ; into a dollar-based string
  [format-nat (-> natural-number/c
                  (and/c string? #rx"[0-9]*\\.[0-9][0-9]"))]))

7.2.5 Contracts on Higher-order Functions

如何描述函数输入以及返回值中的函数类型:

(-> integer? (-> integer? integer?))
(-> (-> integer? integer?) integer?)

7.2.6 Contract Messages with “???”

可以用flat-named-contract给复杂的合约取一个名字:

(module improved-bank-server racket
    (provide
     (contract-out
      [deposit (-> (flat-named-contract
                    'amount
                    (λ (x)
                      (and (number? x) (integer? x) (>= x 0))))
                   any)]))

7.2.7 Dissecting a contract error message

对合约错误信息进行解读。

7.3 Contracts on Functions in General

->只能应用于参数个数固定的函数,对于其他类型的参数,需要使用->*以及->i

7.3.1 Optional Arguments

关于可选参数的一个例子:

(provide
 (contract-out
  ; pad the given str left and right with
  ; the (optional) char so that it is centered
  [string-pad-center (->* (string? natural-number/c)
                          (char?)
                          string?)]))

->*后面先通过一个连对执行必选参数,然后通过额外的连对指定可选参数,最后指定返回值类型。

7.3.2 Rest Arguments

在合约中指定可变参数需要通过关键字的辅助:

(provide
 (contract-out
  [max-abs (->* (real?) () #:rest (listof real?) real?)]))

上述:

  • 必选参数(real?)
  • 可选擦书()
  • 可变参数#:rest (listof real?)
  • 返回值real?

7.3.3 Keyword Arguments

对于关键字参数,直接按照关键字指定即可:

(provide (contract-out
          [ask-yes-or-no-question
           (-> string?
               #:default boolean?
               #:title string?
               #:width exact-integer?
               #:height exact-integer?
               boolean?)]))

7.3.4 Optional Keyword Arguments

可选关键字参数:

(provide (contract-out
          [ask-yes-or-no-question
           (->* (string?
                 #:default boolean?)
                (#:title string?
                 #:width exact-integer?
                 #:height exact-integer?)
                boolean?)]))

7.3.5 Contracts for case-lambda

对于case-lambda的contract定义可能比较特别。对于

(provide (contract-out
          [report-cost
           (case->
            (integer? integer? . -> . string?)
            (string? . -> . string?))]))

其对应的case-lambda是:

(define report-cost
  (case-lambda
    [(lo hi) (format "between $~a and $~a" lo hi)]
    [(desc) (format "~a of dollars" desc)]))

7.3.6 ~ 7.3.9

7.4 Contracts: A Thorough Example

对argmax定义contracts。

#lang racket

(define (argmax f lov) ...)

(provide
  (contract-out
    [argmax (-> (-> any/c real?) (and/c pair? list?) any/c)]))

剩余略

7.5 Contracts on Structures

7.5.1 Guarantees for a Specific Value

可以通过struct/c来指明导出的是一个struct。

7.5.2 Guarantees for All Values

7.5.3 Checking Properties of Data Structures

7.6 Abstract Contracts using #:exists and #:∃

可以通过#:exists亦或#:∃创建抽象的合约类型,以抹掉原有类型的痕迹。

7.7 Additional Examples

7.8 Building New Contracts

合约的内部表示其实是一个接受特定参数并返回projection的。

具体内容略。

7.9 Gotchas

略。

(本篇完)

Categories