美文网首页
7.2简单契约和函数

7.2简单契约和函数

作者: jarod_chan | 来源:发表于2015-12-28 20:29 被阅读75次

    数学函数有一个领域和范围。领域代表能够接受的参数,范围代表产生的值。传统表示方法如下

    f:A->B

    程序语言的函数也有领域和范围,契约能保证函数值接受领域的值和只产生范围的值。->创建一个函数契约。

      #lang racket
      (provide (contract-out
                    [deposit (-> number? any)]
                    [balance (-> number?)]))
      (define amout 0)
      (define (deposit a) (set! amount (+ amount a)))
      (define (balance) amout)
    

    这个模块导出了两个函数

    • deposit 接受一个数字返回某个值
    • balance 返回一个数字
      但一个模块导出一个函数,它建立了两个交流通道。它自己作为服务器,导入这个函数的模块则作为客户端。如果客户端调用一个函数,它发送一个值到服务器。相反的,如果这个函数调用结束函数返回一个值,服务器发送一个值给客户端。客户端和服务器的区别很重要,因为当出现错误,会把错误归咎到某个部分。
      ->本身并不是契约,它是一个契约联接符。

    7.2.1->样式

    如果你使用数学函数,你可能更愿意让契约箭头出现在领域和范围之间,而不是在开头。事实上,你确实可以这么做

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

    在racket里面一个s表达式如果含有两个点的符号在中间,读取器则会重排s表达式让符号在前面。所以它等价于

      (-> number? any)
    

    7.2.2使用 define/contract 和 ->

    define/contract形式也能使用契约来定义。

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

    这种使用方式有两个影响
    1 因为调用的时候总是检查契约,即使在模块内部。所以,它会造成性能下降。特别是在循环和递归的时候。
    2 在某些情况下,同模块调用时,可能有一些更加宽松的输入限制。在这种情况下,define/contract建立的限制太严格了。
    7.2.3any和any/c
    any契约匹配任何一种结果,它只能使用在范围的位置。除此之外,我们能使用void?契约,表示函数会返回(void)值。void?契约会被契约检测系统检查,当函数返回值的时候。相反的,any契约告诉契约检测系统不用检查返回值,它告诉客户端模块服务器不会对函数返回值作出任何承诺,可以单个值也可以多值。
    any/c和any类似,但是,any/c代表一个单值。

      (define (f x) (values (+ x 1) (- x 1)))
    

    上面这个函数匹配(-> integer? any),但是不匹配(-> integer? any/c)。当承诺返回单个结果很重要的时候使用any/c,当竟可能少的承诺返回结果时使用any。

    7.2.4定制自己的契约

      #lang racket
       (define (amount? a)
        (and (number? a) (integer? a) (exact? a) (>= a 0)))
    
        (provide (contract-out
          ; an amount is a natural number of cents
          ; is the given number an amount?
          [deposit (-> amount? any)]
          [amount? (-> any/c boolean?)]
          [balance (-> amount?)]))
    
          (define amount 0)
          (define (deposit a) (set! amount (+ amount a)))
          (define (balance) amount)
    

    上面这个模块自定义了amount?函数作为契约。如果客户端无法理解amount?,那就没有任何意义。所以模块也导出了amount?谓词本身。我们可使用natural-number/c替代amount?,因为它们的约束恰好相同。

      (provide (contract-out
                    [depsoit (-> natural-number/c any)]
                    [balance (-> natural-number/c)]))
    

    每个接受一个参数的函数都能被当做谓词在契约里使用。所以契约组合器变的很有用。比如and/c和or/c.
    (define amount/c
    (and/c number? integer? exact? (or/c positive? zero?)))
    (provide (contract-out
    [deposit (-> amount/c any)]
    [balance (-> amount/c)]))

    7.2.5 高阶函数契约

    函数契约不是只能使用简单的谓词在他们的领域和范围。任何这里讨论的组合器,包括函数契约本身,都能被当做契约来使用在函数的参数和结果上。

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

    它表示接受一个参数并返回一个函数,这个函数接受一个参数并最终返回一个结果。
    类似的还有

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

    7.2.6 契约消息tempN

    如果在契约里传入一个lambda表达式(匿名函数),则调用的时候错误信息会显示tempN,如果想改进这种错误提示,可以先申明一个有函数名的谓词判断函数,那么提示就会变得清晰。

    7.2.7解释契约错误信息

    一般来说,契约的错误信息包含下面留个方面

    • 一个和约束相关的方法或函数,并有短语contract violation或者broken its contract。这个短语取决于客户端还是服务器违反了契约。
      deposit: contract violation
    • 一个描述希望值和实际值
      expected: amount
      given:-10
    • 完整的契约加上那个被违反了
      in: the ist argument of
      (-> amount any)
    • 契约所属的模块
      contract from: improved-bank-server
    • 归咎于谁
      blaming:top-level
      (assuming the contract is correct)
    • 源代码出现的位置
      at: eval:5.0

    相关文章

      网友评论

          本文标题:7.2简单契约和函数

          本文链接:https://www.haomeiwen.com/subject/oufvhttx.html