球拍合同依赖评估两次?

#lang racket

(module inside racket
  (provide
    (contract-out
      [dummy (->i ([x       (lambda (x) (begin (displayln 0) #t))]
                   [y (x)   (lambda (y) (begin (displayln 1) #t))]
                   [z (x y) (lambda (z) (begin (displayln 2) #t))]
                   )
                  any
                  )]
      )
    )

  (define (dummy x y z) #t)
  )

(require 'inside)

(dummy 1 2 3)

输出是

0
0
1
1
2
#t

我不清楚为什么将x和y作为依赖关系需要相应的后卫再次开火.

– > i http://docs.racket-lang.org/reference/function-contracts.html#%28form._%28%28lib.racket%2Fcontract%2Fbase..rkt%29.-~3ei%29%29的文档似乎没有提到这种行为.

任何人都可以对此有所了解吗?

最佳答案
这对我来说和你一样令人困惑,所以我抓住机会到ask this question on the Racket mailing list.接下来的是试图总结我发现的东西.

– > i组合子使用文件Correct Blame for Contracts中提出的indy blame语义生成一个依赖契约.本文提出的关键思想是,依赖契约,实际上可能有三个方可能需要归咎于契约违法行为.

在正常的职能合同中,有两个可能有罪的政党.第一个是最明显的一个,即调用者.例如:

> (define/contract (foo x)
    (integer? . -> . string?)
    (number->string x))
> (foo "hello")
foo: contract violation
  expected: integer?
  given: "hello"
  in: the 1st argument of
      (-> integer? string?)
  contract from: (function foo)
  blaming: anonymous-module
   (assuming the contract is correct)

第二个潜在的有罪方是功能本身;也就是说,实施可能与合同不匹配:

> (define/contract (bar x)
    (integer? . -> . string?)
    x)
> (bar 1)
bar: broke its own contract
  promised: string?
  produced: 1
  in: the range of
      (-> integer? string?)
  contract from: (function bar)
  blaming: (function bar)
   (assuming the contract is correct)

这两种情况都非常明显.然而, – > i合同引入了第三个潜在的有罪方:合同本身.

由于 – > i合约可以在合约附加时执行任意表达,因此他们可能会违反自己.考虑以下合同:

(->i ([mk-ctc (integer? . -> . contract?)])
      [val (mk-ctc) (mk-ctc "hello")])
     [result any/c])

这是一个有些愚蠢的合同,但很容易看出它是一个顽皮的合同.它承诺只用整数调用mk-ctc,但依赖表达式(mk-ctc“hello”)用字符串调用它!责怪调用函数显然是错误的,因为它无法控制无效契约,但是责备契约函数也可能是错误的,因为契约可以与它附加的函数完全隔离定义.

为了便于说明,请考虑一个多模块示例:

#lang racket

(module m1 racket
  (provide ctc)
  (define ctc
    (->i ([f (integer? . -> . integer?)]
          [v (f) (λ (v) (> (f v) 0))])
         [result any/c])))

(module m2 racket
  (require (submod ".." m1))
  (provide (contract-out [foo ctc]))
  (define (foo f v)
    (f #f)))

(require 'm2)

在此示例中,ctc契约在m1子模块中定义,但使用契约的函数在单独的子模块m2中定义.这里有两种可能的指责方案:

> foo函数显然是无效的,因为它将f应用于#f,尽管该参数指定了(整数?. – >.integer?).你可以通过调用foo来实现这一点:

> (foo add1 0)
foo: broke its own contract
  promised: integer?
  produced: #f
  in: the 1st argument of
      the f argument of
      (->i
       ((f (-> integer? integer?))
        (v (f) (λ (v) (> (f v) 0))))
       (result any/c))
  contract from: (anonymous-module m2)
  blaming: (anonymous-module m2)
   (assuming the contract is correct)

我强调合同错误中包括责备信息的地方,你可以看到它归咎于m2,这是有道理的.这不是一个有趣的案例,因为它是非独立案件中提到的第二个潜在的指责方.
>但是,ctc合约实际上有点不对劲!请注意,v上的契约将f应用于v,但它从不检查v是否为整数.因此,如果v是其他内容,则将以无效的方式调用f.您可以通过为v赋予非整数值来查看此行为:

> (foo add1 "hello")
foo: broke its own contract
  promised: integer?
  produced: "hello"
  in: the 1st argument of
      the f argument of
      (->i
       ((f (-> integer? integer?))
        (v (f) (λ (v) (> (f v) 0))))
       (result any/c))
  contract from: (anonymous-module m1)
  blaming: (anonymous-module m1)
   (assuming the contract is correct)

合同错误的顶部是相同的(Racket为这类合同违规提供了相同的“打破自己的合同”消息),但是责备信息是不同的!它现在归咎于m1,这是合同的实际来源.这是indy blame派对.

这种区别意味着合同必须两次应用.它将它们应用于每个不同的责备方的信息:首先它将它们与合同责任一起应用,然后它将它们应用于函数责任.

从技术上讲,这可以避免持平合约,因为在初始合约附加过程之后,持平合约永远不会发出违约合约的信号.然而, – > i组合器目前没有实现任何这样的优化,因为它可能不会对性能产生重大影响,并且合同实现已经相当复杂(尽管如果有人想要实现它,它可能会公认).

一般来说,合同预计是无国籍和幂等的(平板合约预计是简单的谓词),所以并不能保证不会发生这种情况,而且 – >我只是用它来实现其罚款 – 粗粒度的责备信息.

事实证明, – > d合约组合器根本没有抓住这个问题,所以add1最终会在这里引发合同违规.这就是为什么 – >我被创造了,这就是为什么 – >我更喜欢 – >> d.

转载注明原文:球拍合同依赖评估两次? - 代码日志