새 계약 컴비네이터 만들기

새 계약 컴비네이터 만들기 (Building New Contract Combinators)

Racket의 계약(contract) 시스템은 사용자 정의 계약 컴비네이터(combinator)를 만들어 확장할 수 있어요. 이 문서에서는 make-contract 계열 함수와 blame 객체 조작, 그리고 계약을 구조체로 정의하는 방법을 설명합니다.

출처: Racket Reference

본문

(require racket/contract/combinator) package: base
procedure
(make-contract
 [#:name name
  #:first-order first-order
  #:late-neg-projection late-neg-proj
  #:collapsible-late-neg-projection collapsible-late-neg-proj
  #:val-first-projection val-first-proj
  #:projection proj
  #:stronger stronger
  #:equivalent equivalent
  #:list-contract? is-list-contract?])
→ contract?
  name : any/c = 'anonymous-contract
  first-order : (-> any/c any/c) = (λ (x) #t)
  late-neg-proj : (or/c #f (-> blame? (-> any/c any/c any/c))) = #f
  collapsible-late-neg-proj : (or/c #f (-> blame? (values (-> any/c any/c any/c) collapsible-contract?))) = #f
  val-first-proj : (or/c #f (-> blame? (-> any/c (-> any/c any/c)))) = #f
  proj : (-> blame? (-> any/c any/c)) =
  (λ (b)
    (λ (x)
      (if (first-order x)
          x
          (raise-blame-error
           b x
           '(expected: "~a" given: "~e")
           name x))))
  stronger : (or/c #f (-> contract? contract? boolean?)) = #f
  equivalent : (or/c #f (-> contract? contract? boolean?)) = #f
  is-list-contract? : boolean? = #f

procedure
(make-chaperone-contract
 [#:name name
  #:first-order first-order
  #:late-neg-projection late-neg-proj
  #:collapsible-late-neg-projection collapsible-late-neg-proj
  #:val-first-projection val-first-proj
  #:projection proj
  #:stronger stronger
  #:equivalent equivalent
  #:list-contract? is-list-contract?])
→ chaperone-contract?
  name : any/c = 'anonymous-chaperone-contract
  first-order : (-> any/c any/c) = (λ (x) #t)
  late-neg-proj : (or/c #f (-> blame? (-> any/c any/c any/c))) = #f
  collapsible-late-neg-proj : (or/c #f (-> blame? (values (-> any/c any/c any/c) collapsible-contract?))) = #f
  val-first-proj : (or/c #f (-> blame? (-> any/c (-> any/c any/c)))) = #f
  proj : (-> blame? (-> any/c any/c)) =
  (λ (b)
    (λ (x)
      (if (first-order x)
          x
          (raise-blame-error
           b x
           '(expected: "~a" given: "~e")
           name x))))
  stronger : (or/c #f (-> contract? contract? boolean?)) = #f
  equivalent : (or/c #f (-> contract? contract? boolean?)) = #f
  is-list-contract? : boolean? = #f

procedure
(make-flat-contract
 [#:name name
  #:first-order first-order
  #:late-neg-projection late-neg-proj
  #:collapsible-late-neg-projection collapsible-late-neg-proj
  #:val-first-projection val-first-proj
  #:projection proj
  #:stronger stronger
  #:equivalent equivalent
  #:list-contract? is-list-contract?])
→ flat-contract?
  name : any/c = 'anonymous-flat-contract
  first-order : (-> any/c any/c) = (λ (x) #t)
  late-neg-proj : (or/c #f (-> blame? (-> any/c any/c any/c))) = #f
  collapsible-late-neg-proj : (or/c #f (-> blame? (values (-> any/c any/c any/c) collapsible-contract?))) = #f
  val-first-proj : (or/c #f (-> blame? (-> any/c (-> any/c any/c)))) = #f
  proj : (-> blame? (-> any/c any/c)) =
  (λ (b)
    (λ (x)
      (if (first-order x)
          x
          (raise-blame-error
           b x
           '(expected: "~a" given: "~e")
           name x))))
  stronger : (or/c #f (-> contract? contract? boolean?)) = #f
  equivalent : (or/c #f (-> contract? contract? boolean?)) = #f
  is-list-contract? : boolean? = #f

이 함수들은 각각 단순 고차 계약(simple higher-order contracts), 챠페론 계약(chaperone contracts), 그리고 플랫 계약(flat contracts)을 만듭니다. 그들은 모두 세 개의 선택적 인자 집합을 받습니다: 이름, 일차(첫째-계층, first-order) 술어, 그리고 blame 추적 프로젝션(projection)입니다. make-flat-contract에 대해서는 flat-contract-with-explanation도 참고하세요.

name 인자는 위반(violation)이 발생했을 때 계약을 설명하기 위해 display로 렌더링될 임의의 값입니다. 단순 고차 계약의 기본 이름은 anonymous-contract, 챠페론 계약은 anonymous-chaperone-contract, 플랫 계약은 anonymous-flat-contract입니다.

일차 술어 first-order는 계약이 적용되는 값을 결정하는 데 사용됩니다. 이 테스트는 contract-first-order-passes?에 의해, 그리고 간접적으로 or/cfirst-or/c에 의해, 선택할 고차 계약이 여러 개 있을 때 값을 어떤 고차 계약으로 감쌀지 결정하는 데 사용됩니다. 기본값은 어떤 값이든 받아들이지만, 프로젝션 인자의 동작과 일치해야 합니다(방법은 아래 참고). 이 술어는 (contract-first-order-okay-to-give-up?)의 값의 영향을 받아야 합니다(자세한 설명은 그 문서를 참고).

late-neg-proj 인자는 지연 부정 프로젝션(late neg projection)을 통해 계약을 적용하는 동작을 정의합니다. 그것이 제공되면, 한 당사자(party)가 빠져 있는 blame 객체를 받아들입니다(blame-missing-party?도 참고). 그런 다음 계약을 받는 값과 빠진 blame 당사자의 이름(이 순서로)을 둘 다 받아들이는 함수를 반환해야 합니다. 결과는 값(계약을 강제하기 위해 챠페론이나 impersonator로 적절히 감싼)이거나, raise-blame-error를 사용해 계약 위반을 알려야 합니다. 기본값은 #f입니다.

collapsible-late-neg-proj 인자는 축소(collapsing)를 지원하는 계약을 위해 late-neg-proj 인자의 자리를 차지합니다. 그것이 제공되면, 한 당사자가 빠져 있는 blame 객체를 받아들입니다. 두 값을 반환해야 합니다. 첫 번째 값은 계약을 받는 값과 빠진 blame 당사자의 이름(이 순서로)을 둘 다 받아들이는 함수여야 합니다. 두 번째 값은 계약의 축소 가능한(collapsible) 표현이어야 합니다.

프로젝션 projval-first-proj는 계약 적용 동작을 정의하는 더 오래된 메커니즘입니다. proj 인자는 두 인자의 커리(curried) 함수입니다: 첫 번째 적용은 blame 객체를 받아들이고, 두 번째는 계약으로 보호할 값을 받아들입니다. 프로젝션은 값(계약의 고차 측면을 강제하기 위해 적절히 감싼)을 만들거나, raise-blame-error를 사용해 계약 위반을 알려야 합니다. 기본 프로젝션은 일차 테스트가 실패하면 오류를 만들고, 그 외에는 값을 그대로 만듭니다. val-first-projlate-neg-proj와 같지만, 추가 커리 계층이 있습니다.

late-neg-proj, proj, val-first-proj, first-order 중 적어도 하나는 #f가 아니어야 합니다.

프로젝션 인자들(late-neg-proj, proj, val-first-proj)은 일차 인자와 동기화되어야 합니다. 특히, 일차 인자가 어떤 값에 대해 #f를 반환하면, 프로젝션들은 그 값에 대해 blame 오류를 일으켜야 하고, 일차 인자가 어떤 값에 대해 #t를 반환하면, 프로젝션은 (나중에 고차 상호작용이 없지 않는 한) 그 값에 대해 어떤 blame도 알리지 않아야 합니다. 다시 말해, 플랫 계약의 경우 일차와 프로젝션 인자는 같은 술어를 검사해야 합니다. 편의상 기본 프로젝션은 일차 인자를 사용해서, 그것이 #f를 반환하면 오류를 알리고 그 외에는 결코 알리지 않습니다.

챠페론 계약에 대한 프로젝션은 원래의 계약되지 않은 값과 비교했을 때 chaperone-of?를 통과하는 값을 만들어야 합니다. 플랫 계약에 대한 프로젝션은 일차가 그러할 때 정확히 실패해야 하고, 그 외에는 입력 값을 그대로 만들어야 합니다. 플랫 계약을 적용하면 술어의 적용, 프로젝션, 또는 둘 다가 발생할 수 있습니다; 따라서 둘은 일관되어야 합니다. 별도의 프로젝션이 존재하는 것은 더 구체적인 오류 메시지를 제공하기 위한 것입니다. 대부분의 플랫 계약은 명시적인 프로젝션을 제공할 필요가 없습니다.

stronger 인자는 contract-stronger?를 구현하는 데 사용됩니다. 첫 번째 인자는 항상 계약 자체이고, 두 번째 인자는 contract-stronger?의 두 번째 인자로 전달된 무엇이든입니다. stronger 인자가 제공되지 않으면, 플랫 계약과 챠페론 계약에는 그 인자들을 equal?로 비교하는 기본값이 사용됩니다. make-contract로 만들어진 impersonator 계약이 stronger 인자를 제공하지 않으면 contract-stronger?#f를 반환합니다.

마찬가지로, equivalent 인자는 contract-equivalent?를 구현하는 데 사용됩니다. 그것이 제공되지 않거나 #false가 제공되면, 챠페론과 플랫 계약에는 equal?가 사용되고, 그 외에는 (λ (x y) #f)가 사용됩니다.

is-list-contract? 인자는 list-contract? 술어가 이 계약이 list? 값만 받아들이는 계약인지 결정하는 데 사용됩니다.

예시:

> (define int/c
    (make-flat-contract #:name 'int/c #:first-order integer?))
> (contract int/c 1 'positive 'negative)
1
> (contract int/c "not one" 'positive 'negative)
eval:4:0: broke its own contract
promised: int/c
produced: "not one"
in: int/c
contract from: positive
blaming: positive
(assuming the contract is correct)
> (int/c 1)
#t
> (int/c "not one")
#f

> (define int->int/c
    (make-contract
     #:name 'int->int/c
     #:first-order
     (λ (x) (and (procedure? x) (procedure-arity-includes? x 1)))
     #:projection
     (λ (b)
       (let ([domain ((contract-projection int/c) (blame-swap b))]
             [range ((contract-projection int/c) b)])
         (λ (f)
           (if (and (procedure? f) (procedure-arity-includes? f 1))
               (λ (x) (range (f (domain x))))
               (raise-blame-error
                b f
                '(expected "a function of one argument" given: "~e")
                f)))))))
> (contract int->int/c "not fun" 'positive 'negative)
eval:8:0: broke its own contract;
promised a function of one argument
produced: "not fun"
in: int->int/c
contract from: positive
blaming: positive
(assuming the contract is correct)

> (define halve
    (contract int->int/c (λ (x) (/ x 2)) 'positive 'negative))
> (halve 2)
1
> (halve 1/2)
halve: contract violation
expected: int/c
given: 1/2
in: int->int/c
contract from: positive
blaming: negative
(assuming the contract is correct)
> (halve 1)
halve: broke its own contract
promised: int/c
produced: 1/2
in: int->int/c
contract from: positive
blaming: positive
(assuming the contract is correct)

base 패키지의 6.0.1.13 버전에서 변경됨: #:list-contract? 인자가 추가되었습니다. 6.90.0.30 버전에서 변경됨: #:equivalent 인자가 추가되었습니다. 7.1.0.10 버전에서 변경됨: #:collapsible-late-neg-projection 인자가 추가되었습니다.

procedure
(build-compound-type-name c/s ...) → any
  c/s : any/c

계약의 이름으로 사용될 S-표현식을 만듭니다. 인자들은 계약 또는 심볼이어야 합니다. 그것은 인자들 주위에 괄호를 감싸고, 제공된 어떤 계약에서든 이름을 추출합니다.

procedure
(coerce-contract id v) → contract?
  id : symbol?
  v : any/c

일반 Racket 값을 계약 구조체의 인스턴스로 변환하며, 계약의 설명에 따라 변환합니다.

v가 강제 가능한(coercible) 값 중 하나가 아니면, coerce-contract는 오류 메시지에 첫 번째 인자를 사용해 오류를 알립니다.

procedure
(coerce-contracts id vs) → (listof contract?)
  id : symbol?
  vs : (listof any/c)

vs의 모든 인자를 (coerce-contract/f를 통해) 계약으로 강제 변환하고, 그 중 하나라도 계약이 아니면 오류를 알립니다. 오류 메시지는 id라는 이름의 함수가 vs를 전체 인자 리스트로 받았다고 가정합니다.

procedure
(coerce-chaperone-contract id v) → chaperone-contract?
  id : symbol?
  v : any/c

coerce-contract와 같지만, 결과가 임의의 계약이 아니라 챠페론 계약이어야 합니다.

procedure
(coerce-chaperone-contracts id vs) → (listof chaperone-contract?)
  id : symbol?
  vs : (listof any/c)

coerce-contracts와 같지만, 결과가 임의의 계약이 아니라 챠페론 계약이어야 합니다.

procedure
(coerce-flat-contract id v) → flat-contract?
  id : symbol?
  v : any/c

coerce-contract와 같지만, 결과가 임의의 계약이 아니라 플랫 계약이어야 합니다.

procedure
(coerce-flat-contracts id v) → (listof flat-contract?)
  id : symbol?
  v : (listof any/c)

coerce-contracts와 같지만, 결과가 임의의 계약이 아니라 플랫 계약이어야 합니다.

procedure
(coerce-contract/f v) → (or/c contract? #f)
  v : any/c

coerce-contract와 같지만, 값을 계약으로 강제 변환할 수 없으면 #f를 반환합니다.

parameter
(skip-projection-wrapper?) → boolean?
(skip-projection-wrapper? wrap?) → void?
  wrap? : boolean?

= #f

make-chaperone-contractbuild-chaperone-contract-property 함수는 프로젝션들의 결과가 입력의 챠페론이 되도록 인자들을 감쌉니다. 이 감싸기 계층은 어떤 경우에는 계약 검사에 원치 않는 오버헤드를 도입할 수 있어요. 이들 함수 중 하나에 대한 호출의 동적 범위 동안 이 매개변수의 값이 #t이면, 감싸기(그리고 그에 따른 검사)가 건너뛰어집니다.

syntax
(with-contract-continuation-mark blame body ...)
(with-contract-continuation-mark blame+neg-party body ...)

계약 프로파일러(contract profiling 문서 참고)에게 계약 검사가 일어나고 있음을 알리는 연속 마크(continuation mark)를 삽입합니다. 새 컴비네이터를 검사하는 비용이 포함되도록, 지연된(deferred) 고차 검사는 이 폼으로 감싸야 합니다. 일차 검사는 자동으로 인식되며 이 폼을 필요로 하지 않습니다.

컴비네이터의 프로젝션이 완전한 blame 객체(즉, 빠진 blame 당사자가 없는)에 대해 동작하면, blame 객체가 이 폼의 첫 번째 인자여야 합니다. 그렇지 않으면(예: late-neg 프로젝션의 경우), blame 객체와 빠진 당사자의 쌍이 대신 사용되어야 합니다.

base 패키지의 6.4.0.4 버전에서 추가됨.

syntax
(contract-pos/neg-doubling e1 e2)

일부 계약 컴비네이터는 접근과 변경 둘 다를 검사하기 위해, 받은 blame의 일반 버전과 blame-swapped 버전 둘 다로 하위 계약들에 대한 프로젝션을 만들어야 합니다(예: vector/c, vectorof). 그러한 컴비네이터들이 서로 깊이 중첩된 경우, 만들어지는 중첩 프로젝션의 지수적 팽창(exponential explosion) 가능성이 있습니다.

그 팽창을 피하려면, 컴비네이터의 blame 수용 부분에 대한 각 호출을 contract-pos/neg-doubling으로 감싸세요. 그것은 세 값을 반환합니다. 첫 번째는 불리언으로, 다른 두 결과를 어떻게 해석할지 나타냅니다. 불리언이 #t이면, 다른 두 결과는 e1e2의 값이고 우리는 중첩이 너무 깊지 않은 것입니다. 불리언이 #f이면, 우리는 임계값을 지났고, 지수적 둔화의 위험에 있으므로 e1e2를 아직 평가하는 것이 안전하지 않습니다. 그 경우, 마지막 두 결과는 호출될 때 e1e2의 값을 계산하는 썽크(thunk)입니다.

예시로, vectorof는 하위 계약을 위한 프로젝션의 blame 수용 부분에 대한 두 호출을 contract-pos/neg-doubling으로 감쌉니다. 첫 번째 불리언으로 #f를 받으면, 썽크들을 즉시 호출하지 않고, 챠페론된 벡터에 붙이는 개입(interposition) 프로시저가 호출될 때까지 기다립니다. 그런 다음 그것들을 호출합니다(그리고 결과를 캐시합니다). 이것은 프로젝션의 구성을 실제로 필요할 때까지 지연시켜서 지수적 폭발을 피합니다.

base 패키지의 6.90.0.27 버전에서 추가됨.

Blame 객체 (Blame Objects)

이 섹션은 blame 객체와 그것에 대한 연산을 설명합니다.

procedure
(blame? v) → boolean?
  v : any/c

이 술어는 blame 객체를 인식합니다.

procedure
(raise-blame-error b
                    #:missing-party missing-party
                    v fmt v-fmt ...) → none/c
  b : blame?
  missing-party : #f
  v : any/c
  fmt : (or/c string?
              (listof (or/c string? 'given 'given: 'expected 'expected:)))
  v-fmt : any/c

계약 위반을 알립니다. 첫 번째 인자 b는 현재 blame 정보를 기록하는데, 긍정(positive)과 부정(negative) 당사자, 계약의 이름, 값의 이름, 그리고 계약 적용의 소스 위치를 포함합니다. #:missing-party 인자는 blame 당사자 중 하나를 공급합니다. b 객체가 부정 당사자를 공급하지 않고 만들어졌을 때에는 #f가 아니어야 합니다. blame-add-missing-partymake-contractlate-neg-proj 인자 설명을 참고하세요.

두 번째 위치적 인자 v는 계약을 만족시키지 못한 값입니다.

나머지 인자들은 정확한 위반에 특화된 오류 메시지를 지정하는 포맷 문자열 fmt과 그 인자들 v-fmt ...입니다.

fmt가 리스트이면, 요소들은 (문자열 끝에 이미 공백이 없는 한 공백을 추가하면서) 서로 이어 붙여집니다. 심볼은 문자열 대응물로 먼저 대체된 후, b 인자가 swap되었는지 여부(blame-swap 참고)에 따라 'given"produced"로, 'expected"promised"로 대체됩니다.

fmt'given:이나 'expected: 심볼을 포함하면, 그것들은 'given'expected처럼 대체되지만, 대체물은 "Error Message Conventions"의 오류 메시지 지침을 따르도록 "\n " 문자열로 접두어가 붙습니다.

procedure
(blame-add-context blame context
                   [#:important important #:swap? swap?]) → blame?
  blame : blame?
  context : (or/c string? #f)
  important : (or/c string? #f) = #f
  swap? : boolean? = #f

계약의 어떤 부분이 실패했는지를 설명하는(그리고 raise-blame-error가 렌더링하는) 컨텍스트 정보를 blame 오류 메시지에 추가합니다.

context 인자는 계약 부분의 한 계층을 설명하며, 보통 (함수 계약의 경우) "the 1st argument of" 또는 (and/c 계약의 경우) "a conjunct of" 형태입니다.

예를 들어, 이 계약 위반을 고려해 보세요:

> (define/contract f
    (list/c (-> integer? integer?))
    (list (λ (x) x)))
> ((car f) #f))
f: contract violation
expected: integer?
given: #f
in: the 1st argument of
   the 1st element of
   (list/c (-> integer? integer?))
contract from: (definition f)
blaming: top-level
(assuming the contract is correct)
at: eval:2:0

이것은 위반되는 계약의 부분이 integer?의 첫 번째 발생임을 보여줍니다. 왜냐하면 ->list/c 컴비네이터가 각각 내부적으로 blame-add-context를 호출해 오류 메시지의 "in" 뒤에 오는 두 줄을 추가했기 때문이에요.

important 인자는 계약 위반의 시작 부분을 만드는 데 사용됩니다. blame 객체에 추가된 마지막 important 인자가 사용됩니다. class/c 계약은 (->가 계약을 받는 함수의 이름을 알 때) -> 계약처럼 important 인자를 추가합니다.

swap? 인자는 추가 blame 객체를 만들지 않고 컨텍스트 계층을 추가하면서 blame-swap를 호출하는 효과를 갖습니다.

컨텍스트 문자열 인자로 #f를 전달하는 것은 더 이상 관련이 없습니다. 하위 호환성 때문에, context#f일 때 blame-add-contextb를 반환합니다.

base 패키지의 6.90.0.29 버전에서 변경됨: context 인자가 #f인 것은 더 이상 관련이 없습니다.

procedure
(blame-context blame) → (listof string?)
  blame : blame?

blameraise-blame-error에 전달된다면 오류 메시지에 공급될 컨텍스트 정보를 반환합니다.

procedure
(blame-positive b) → any/c
  b : blame?

procedure
(blame-negative b) → any/c
  b : blame?

이 함수들은 blame 객체의 현재 긍정과 부정 당사자의 인쇄 가능한 설명을 만듭니다.

procedure
(blame-contract b) → any/c
  b : blame?

이 함수는 blame 객체(contract-name의 결과)와 연관된 계약의 설명을 만듭니다.

procedure
(blame-value b) → any/c
  b : blame?

이 함수는 계약이 적용된 값의 이름을 만들거나, 이름이 제공되지 않았으면 #f를 만듭니다.

procedure
(blame-source b) → srcloc?
  b : blame?

이 함수는 계약과 연관된 소스 위치를 만듭니다. 소스 위치가 제공되지 않았다면, 구조체의 모든 필드가 #f를 포함할 것입니다.

procedure
(blame-swap b) → blame?
  b : blame?

이 함수는 blame 객체의 긍정과 부정 당사자를 바꿉니다. (blame-add-context도 참고하세요.)

procedure
(blame-original? b) → boolean?
  b : blame?

procedure
(blame-swapped? b) → boolean?
  b : blame?

이 함수들은 주어진 blame 객체의 현재 blame이 (현재 것을 포함하는 복합 계약의) 원래 계약 호출에서와 같은지, 아니면 swap되었는지를 각각 보고합니다. 각각은 다른 것의 부정입니다; 둘 다 편의와 명확성을 위해 제공됩니다.

procedure
(blame-replace-negative b neg) → blame?
  b : blame?
  neg : any/c

b가 부정 위치에 있는 것을 neg로 바꾼다는 점을 제외하면 b와 같은 blame? 객체를 만듭니다.

procedure
(blame-replaced-negative? b) → boolean?
  b : blame?

bblame-replace-negative를 호출한 결과(또는 입력이 blame-replace-negative의 결과였던 다른 함수의 결과)이면 #t를 반환합니다.

procedure
(blame-update b pos neg) → blame?
  b : blame?
  pos : any/c
  neg : any/c

긍정과 부정 당사자에 posneg를 각각 추가한다는 점을 제외하면 b와 같은 blame? 객체를 만듭니다.

procedure
(blame-missing-party? b) → boolean?
  b : blame?

b에 두 당사자가 모두 없으면 #t를 반환합니다.

procedure
(blame-add-missing-party b missing-party) → (and/c blame? (not/c blame-missing-party?))
  b : (and/c blame? blame-missing-party?)
  missing-party : any/c

빠진 당사자가 missing-party로 대체된다는 점을 제외하면 b와 같은 새 blame 객체를 만듭니다.

struct
(struct exn:fail:contract:blame exn:fail:contract (object)
        #:extra-constructor-name make-exn:fail:contract:blame)
  object : blame?

이 예외는 계약 오류를 알리기 위해 일어납니다. object 필드는 계약 위반과 연관된 blame 객체를 포함합니다.

parameter
(current-blame-format) → (-> blame? any/c string? string?)
(current-blame-format proc) → void?
  proc : (-> blame? any/c string? string?)

계약 위반 오류를 구성할 때 사용되는 매개변수입니다. 그 값은 세 개의 인자를 받아들이는 프로시저입니다:

  • 위반에 대한 blame 객체,
  • 계약이 적용되는 값,
  • 위반의 종류를 나타내는 메시지.

그 프로시저는 계약 오류 메시지에 넣을 문자열을 반환합니다. 값은 흔히 위반을 나타내는 메시지에 이미 포함되어 있다는 점에 유의하세요.

예시:

> (define (show-blame-error blame value message)
    (string-append
     "Contract Violation!\n"
     (format "Guilty Party: ~a\n" (blame-positive blame))
     (format "Innocent Party: ~a\n" (blame-negative blame))
     (format "Contracted Value Name: ~a\n" (blame-value blame))
     (format "Contract Location: ~s\n" (blame-source blame))
     (format "Contract Name: ~a\n" (blame-contract blame))
     (format "Offending Value: ~s\n" value)
     (format "Offense: ~a\n" message)))
> (current-blame-format show-blame-error)

> (define/contract (f x)
    (-> integer? integer?)
    (/ x 2))
> (f 2)
1
> (f 1)
Contract Violation!
Guilty Party: (function f)
Innocent Party: top-level
Contracted Value Name: f
Contract Location: #(struct:srcloc eval 4 0 4 1)
Contract Name: (-> integer? integer?)
Offending Value: 1/2
Offense: promised: integer?
produced: 1/2

> (f 1/2)
Contract Violation!
Guilty Party: top-level
Innocent Party: (function f)
Contracted Value Name: f
Contract Location: #(struct:srcloc eval 4 0 4 1)
Contract Name: (-> integer? integer?)
Offending Value: 1/2
Offense: expected: integer?
given: 1/2

계약으로서의 구조체 (Contracts as structs)

prop:contract 속성은 임의의 구조체가 계약으로 작동하게 합니다. prop:chaperone-contract 속성은 임의의 구조체가 챠페론 계약으로 작동하게 합니다. prop:chaperone-contractprop:contract를 상속하므로, 챠페론 계약 구조체는 일반 계약으로도 작동할 수 있습니다. prop:flat-contract 속성은 임의의 구조체가 플랫 계약으로 작동하게 합니다. prop:flat-contractprop:chaperone-contractprop:procedure 둘 다를 상속하므로, 플랫 계약 구조체는 챠페론 계약, 일반 계약, 그리고 술어 프로시저로도 작동할 수 있습니다.

value
prop:contract : struct-type-property?

value
prop:chaperone-contract : struct-type-property?

value
prop:flat-contract : struct-type-property?

이 속성들은 구조체를 각각 계약 또는 플랫 계약으로 선언합니다. prop:contract의 값은 build-contract-property로 구성된 계약 속성이어야 합니다. 마찬가지로 prop:chaperone-contract의 값은 build-chaperone-contract-property로 구성된 챠페론 계약 속성이어야 하고, prop:flat-contract의 값은 build-flat-contract-property로 구성된 플랫 계약 속성이어야 합니다.

value
prop:contracted : struct-type-property?

value
impersonator-prop:contracted : impersonator-property?

이 속성들은 계약 값을 보호된 구조체, 챠페론, 또는 impersonator 값에 붙입니다. has-contract? 함수는 이 속성 중 하나를 가진 값에 대해 #t를 반환하고, value-contract는 (값에 대한 계약일 것으로 기대되는) 속성에서 값을 추출합니다.

value
prop:blame : struct-type-property?

value
impersonator-prop:blame : impersonator-property?

이 속성들은 blame 정보를 보호된 구조체, 챠페론, 또는 impersonator 값에 붙입니다. has-blame? 함수는 이 속성 중 하나를 가진 값에 대해 #t를 반환하고, value-blame은 속성에서 값을 추출합니다.

값은 값에 대한 계약의 blame 레코드이거나, 빠진 당사자가 있는 blame 레코드와 그 빠진 당사자의 cons 쌍일 것으로 기대됩니다. value-blame 함수는 blame-add-missing-party를 사용해 쌍의 인자들을 완전한 blame 레코드로 재조립합니다. 값이 이 속성 중 하나를 가지지만, 값이 blame 객체이거나 car 위치가 blame 객체인 쌍이 아니면, has-blame?#f를 반환하지만 value-blame#f를 반환합니다.

procedure
(build-flat-contract-property
 [#:name get-name
  #:first-order get-first-order
  #:late-neg-projection late-neg-proj
  #:collapsible-late-neg-projection collapsible-late-neg-proj
  #:val-first-projection val-first-proj
  #:projection get-projection
  #:stronger stronger
  #:equivalent equivalent
  #:generate generate
  #:list-contract? is-list-contract?])
→ flat-contract-property?

  get-name : (or/c #f (-> contract? any/c)) = (λ (c) 'anonymous-flat-contract)
  get-first-order : (-> contract? (-> any/c boolean?)) = (λ (c) (λ (x) #t))
  late-neg-proj : (or/c #f (-> contract? (-> blame? (-> any/c any/c any/c)))) = #f
  collapsible-late-neg-proj : (or/c #f (-> contract? (-> blame? (values (-> any/c any/c any/c) collapsible-contract?)))) = #f
  val-first-proj : (or/c #f (-> contract? blame? (-> any/c (-> any/c any/c)))) = #f
  get-projection : (-> contract? (-> blame? (-> any/c any/c))) =
  (λ (c)
    (λ (b)
      (λ (x)
        (if ((get-first-order c) x)
            x
            (raise-blame-error
             b x '(expected: "~a" given: "~e")
             (get-name c) x)))))
  stronger : (or/c (-> contract? contract? boolean?) #f) = #f
  equivalent : (or/c #f (-> contract? contract? boolean?)) = #f
  generate : (->i ([(c contract?)])
                  [result (c)
                          (-> exact-nonnegative-integer?
                              (or/c (-> (or/c contract-random-generate-fail? c)) #f))]) = (λ (c) (λ (fuel) #f))
  is-list-contract? : (-> contract? boolean?) = (λ (c) #f)

procedure
(build-chaperone-contract-property
 [#:name get-name
  #:first-order get-first-order
  #:late-neg-projection late-neg-proj
  #:collapsible-late-neg-projection collapsible-late-neg-proj
  #:val-first-projection val-first-proj
  #:projection get-projection
  #:stronger stronger
  #:equivalent equivalent
  #:generate generate
  #:exercise exercise
  #:list-contract? is-list-contract?])
→ chaperone-contract-property?

  get-name : (or/c #f (-> contract? any/c)) = (λ (c) 'anonymous-chaperone-contract)
  get-first-order : (-> contract? (-> any/c boolean?)) = (λ (c) (λ (x) #t))
  late-neg-proj : (or/c #f (-> contract? (-> blame? (-> any/c any/c any/c)))) = #f
  collapsible-late-neg-proj : (or/c #f (-> contract? (-> blame? (values (-> any/c any/c any/c) collapsible-contract?)))) = #f
  val-first-proj : (or/c #f (-> contract? blame? (-> any/c (-> any/c any/c)))) = #f
  get-projection : (-> contract? (-> blame? (-> any/c any/c))) =
  (λ (c)
    (λ (b)
      (λ (x)
        (if ((get-first-order c) x)
            x
            (raise-blame-error
             b x '(expected: "~a" given: "~e")
             (get-name c) x)))))
  stronger : (or/c (-> contract? contract? boolean?) #f) = #f
  equivalent : (or/c #f (-> contract? contract? boolean?)) = #f
  generate : (->i ([(c contract?)])
                  [result (c)
                          (-> exact-nonnegative-integer?
                              (or/c (-> (or/c contract-random-generate-fail? c)) #f))]) = (λ (c) (λ (fuel) #f))
  exercise : (->i ([(c contract?)])
                  [result (c)
                          (-> exact-nonnegative-integer?
                              (values (-> c void?) (listof contract?)))]) = (λ (c) (λ (fuel) (values void '())))
  is-list-contract? : (-> contract? boolean?) = (λ (c) #f)

procedure
(build-contract-property
 [#:name get-name
  #:first-order get-first-order
  #:late-neg-projection late-neg-proj
  #:collapsible-late-neg-projection collapsible-late-neg-proj
  #:val-first-projection val-first-proj
  #:projection get-projection
  #:stronger stronger
  #:equivalent equivalent
  #:generate generate
  #:exercise exercise
  #:list-contract? is-list-contract?])
→ contract-property?

  get-name : (or/c #f (-> contract? any/c)) = (λ (c) 'anonymous-contract)
  get-first-order : (-> contract? (-> any/c boolean?)) = (λ (c) (λ (x) #t))
  late-neg-proj : (or/c #f (-> contract? (-> blame? (-> any/c any/c any/c)))) = #f
  collapsible-late-neg-proj : (or/c #f (-> contract? (-> blame? (values (-> any/c any/c any/c) collapsible-contract?)))) = #f
  val-first-proj : (or/c #f (-> contract? blame? (-> any/c (-> any/c any/c)))) = #f
  get-projection : (-> contract? (-> blame? (-> any/c any/c))) =
  (λ (c)
    (λ (b)
      (λ (x)
        (if ((get-first-order c) x)
            x
            (raise-blame-error
             b x '(expected: "~a" given: "~e")
             (get-name c) x)))))
  stronger : (or/c (-> contract? contract? boolean?) #f) = #f
  equivalent : (or/c #f (-> contract? contract? boolean?)) = #f
  generate : (->i ([(c contract?)])
                  [result (c)
                          (-> exact-nonnegative-integer?
                              (or/c (-> (or/c contract-random-generate-fail? c)) #f))]) = (λ (c) (λ (fuel) #f))
  exercise : (->i ([(c contract?)])
                  [result (c)
                          (-> exact-nonnegative-integer?
                              (values (-> c void?) (listof contract?)))]) = (λ (c) (λ (fuel) (values void '())))
  is-list-contract? : (-> contract? boolean?) = (λ (c) #f)

이 함수들은 각각 prop:contract, prop:chaperone-contract, prop:flat-contract의 인자들을 만듭니다.

계약 속성(contract property)은 구조체가 계약으로 사용될 때의 동작을 지정합니다. 그것은 일곱 가지 속성으로 지정됩니다:

  • get-name — 계약 위반의 일부로 쓸 설명을 만들며, 기본값은 항상 'anonymous-contract, 'anonymous-chaperone-contract, 또는 'anonymous-flat-contract를 만드는 함수입니다;
  • get-first-ordercontract-first-order-passes?가 사용할 일차 술어를 만듭니다;
  • late-neg-proj — 계약의 동작을 정의하는 blame 추적 프로젝션을 만듭니다 (get-projectionval-first-proj 인자도 프로젝션을 지정하지만 다른 시그니처를 사용합니다. 그것들은 하위 호환성을 위해 여기에 있습니다);
  • collapsible-late-neg-projlate-neg-proj와 비슷하게 계약의 동작을 정의하는 blame 추적 프로젝션을 만들며, 이 함수는 추가로 계약의 축소 가능한 동작을 지정합니다;
  • stronger — 이 계약(첫 번째 인자로 전달됨)이 다른 계약(두 번째 인자로 전달됨)보다 더 강한지 결정하는 술어이며, 기본값은 항상 #f를 반환합니다;
  • equivalent — 이 계약(첫 번째 인자로 전달됨)이 다른 계약(두 번째 인자로 전달됨)과 동등한지 결정하는 술어입니다. 플랫과 챠페론 계약의 기본값은 equal?이고 impersonator 계약은 #f를 반환합니다;
  • generate — 계약과 일치하는 임의의 값을 생성하는 썽크를 반환합니다(contract-random-generate-fail을 사용해 실패를 나타냄) 또는 이 계약에 대한 임의 생성을 지원하지 않음을 나타내는 #f를 반환합니다;
  • exercise — 계약과 일치하는 값을 연습(exercise)하는 함수(예: 함수 계약이면 함수를 호출할 수 있음)와, 이 과정에 의해 생성될 값들의 계약 리스트를 반환합니다;
  • 그리고 is-list-contract?flat-contract?가 이 계약이 list?만 받아들이는지 결정하는 데 사용합니다.

late-neg-proj, collapsible-late-neg-proj, get-projection, val-first-proj, get-first-order 중 적어도 하나는 #f가 아니어야 합니다.

이 접근자들(accessor)은 (선택적) 키워드 인자로 build-contract-property에 전달되고, 계약 시스템에 의해 적절한 구조체 타입의 인스턴스에 적용됩니다. 그 결과는 make-contract의 인자들과 유사하게 사용됩니다.

챠페론 계약 속성은 구조체가 챠페론 계약으로 사용될 때의 동작을 지정합니다. build-chaperone-contract-property로 지정되며, build-contract-property와 정확히 같은 인자 집합을 받아들입니다. 유일한 차이는 프로젝션 접근자가 원래의 계약되지 않은 값과 비교했을 때 chaperone-of?를 통과하는 값을 반환해야 한다는 것입니다.

플랫 계약 속성은 구조체가 플랫 계약으로 사용될 때의 동작을 지정합니다. build-flat-contract-property로 지정되며, build-contract-property와 비슷한 인자들을 받아들입니다. 차이점은:

  • 프로젝션 접근자가 그 인자를 고차 방식으로 감싸지 않을 것으로 기대되며, 이는 make-flat-contract의 프로젝션에 대한 제약과 유사합니다;
  • #:exercise 키워드 인자가 생략되는데, 플랫 계약에는 관련이 없기 때문입니다.

base 패키지의 6.0.1.13 버전에서 변경됨: #:list-contract? 인자가 추가되었습니다. 6.1.1.4 버전에서 변경됨: generatecontract-random-generate-fail을 반환하도록 허용됩니다. 6.90.0.30 버전에서 변경됨: #:equivalent 인자가 추가되었습니다. 7.1.0.10 버전에서 변경됨: #:collapsible-late-neg-projection 인자가 추가되었습니다.

procedure
(contract-property? v) → boolean?
  v : any/c

procedure
(chaperone-contract-property? v) → boolean?
  v : any/c

procedure
(flat-contract-property? v) → boolean?
  v : any/c

이 술어들은 값이 각각 계약 속성, 챠페론 계약 속성, 또는 플랫 계약 속성인지 감지합니다.

Check Syntax의 의무 정보 (Obligation Information in Check Syntax)

DrRacket의 Check Syntax는 계약 컴비네이터들이 프로그램의 확장된 형태에 남겨 두는 구문 속성들에 따라 계약에 대한 의무(obligation) 정보를 보여줍니다. 이 속성들은 계약이 소스의 어디에 나타나는지, 그리고 계약의 긍정과 부정 위치가 어디에 나타나는지를 나타냅니다.

새 계약 컴비네이터에 대해 Check Syntax가 의무 정보를 보여주게 하려면 다음 속성들을 사용하세요(몇 가지 도우미 매크로와 함수는 아래에 있습니다):

  • 'racket/contract:contract : (vector/c symbol? (listof syntax?) (listof syntax?)) — 이 속성은 계약 컴비네이터를 구현하는 트랜스포머의 결과에 붙어야 합니다. Check Syntax에게 여기가 계약이 시작되는 곳임을 알립니다.

    벡터의 첫 번째 요소는 (eq?의 의미로) 고유한 값이어야 하며, Check Syntax는 그것을 이 계약을 그것의 하위 조각들(다음 두 구문 속성으로 지정됨)과 짝지을 태그로 사용할 수 있습니다.

    벡터의 두 번째와 세 번째 요소는 계약의 조각들에서 온 구문 객체들이며, Check Syntax는 그것들을 색칠합니다. 첫 번째 리스트는 계약의 구현을 제공하는 당사자들(보통 모듈)의 책임인 하위 부분들을 포함해야 합니다. 두 번째 리스트는 클라이언트들의 책임인 하위 부분들을 포함해야 합니다.

    예를 들어, (->* () #:pre #t any/c #:post #t)에서 ->*#:post는 첫 번째 리스트에, #:pre는 두 번째 리스트에 있어야 합니다.

  • 'racket/contract:negative-position : symbol? — 이 속성은 다른 계약일 것으로 기대되는 계약 컴비네이터의 하위 표현식들에 붙어야 합니다. 속성의 값은 이 계약이 어느 것인지를 나타내는 키('racket/contract:contract 속성의 벡터의 첫 번째 요소)여야 합니다.

    이 속성은 표현식의 값이 클라이언트들이 책임지는 계약일 때 사용되어야 합니다.

  • 'racket/contract:positive-position : symbol? — 이 폼은 'racket/contract:negative-position과 같지만, 표현식의 값이 원래의 당사자가 책임져야 하는 계약일 때 사용되어야 합니다.

  • 'racket/contract:contract-on-boundary : symbol? — 이 속성의 존재는 Check Syntax에게 이 지점에서부터 색칠을 시작해야 한다고 알립니다. 그것은 표현식이 계약일 것('racket/contract:contract 속성을 가질 것)을 기대하며, 이 속성은 이 계약이 (모듈) 경계에 있다는 것을 나타냅니다. (속성의 값은 사용되지 않습니다.)

  • 'racket/contract:internal-contract : symbol?'racket/contract:contract-on-boundary처럼, 이 속성의 존재는 색칠을 트리거하지만, 계약을 포함하는 당사자(모듈)가 (이 모듈이 계약과 일치하는 무엇을 익스포트하는지 여부와 무관하게) 계약 위반에 대해 blame될 수 있을 때 사용되기 위한 것입니다. 이것은 ->i 계약에 대해 작용하는데, 계약 자체가 의존성(dependency)을 통해 계약 받는 값들에 접근할 수 있기 때문이에요.

syntax
(define/final-prop header body ...)

header = main-id
       | (main-id id ...)
       | (main-id id ... . id)

(define header body ...)와 같지만, header의 main-id 사용이 'racket/contract:contract 속성(위와 같이)으로 주석이 달립니다.

syntax
(define/subexpression-pos-prop header body ...)

header = main-id
       | (main-id id ...)
       | (main-id id ... . id)

(define header body ...)와 같지만, header의 main-id 사용이 'racket/contract:contract 속성(위와 같이)으로 주석이 달리고 인자들이 'racket/contract:positive-position 속성으로 주석이 달립니다.

새 컴비네이터 구축을 위한 유틸리티 (Utilities for Building New Combinators)

procedure
(contract-stronger? c1 c2) → boolean?
  c1 : contract?
  c2 : contract?

계약 c1c2보다 적은 값 집합 또는 같은 값 집합을 받아들이면 #t를 반환합니다.

같은(c1c2equal?인) 챠페론 계약과 플랫 계약은 항상 서로 더 강한 것으로 간주됩니다.

이 함수는 보수적이므로, c1이 실제로 더 적은 값을 받아들이는데도 #f를 반환할 수 있습니다.

예시:

> (contract-stronger? integer? integer?)
#t
> (contract-stronger? (between/c 25 75) (between/c 0 100))
#t
> (contract-stronger? (between/c 0 100) (between/c 25 75))
#f
> (contract-stronger? (between/c -1.0 0) (between/c 0 10))
#f
> (contract-stronger? (λ (x) (and (real? x) (<= x 0)))
                      (λ (x) (and (real? x) (<= x 100))))
#f
procedure
(contract-equivalent? c1 c2) → boolean?
  c1 : contract?
  c2 : contract?

계약 c1c2와 같은 값 집합을 받아들이면 #t를 반환합니다.

같은(c1c2equal?인) 챠페론 계약과 플랫 계약은 항상 서로 동등한 것으로 간주됩니다.

이 함수는 보수적이므로, c1이 실제로 c2와 같은 값 집합을 받아들이는데도 #f를 반환할 수 있습니다.

예시:

> (contract-equivalent? integer? integer?)
#t
> (contract-equivalent? (non-empty-listof integer?)
                        (cons/c integer? (listof integer?)))
#t
> (contract-equivalent? (λ (x) (and (real? x) (and (number? x) (>= (sqr x) 0))))
                        (λ (x) (and (real? x) (real? x))))
#f

base 패키지의 6.90.0.30 버전에서 추가됨.

procedure
(contract-first-order-passes? contract v) → boolean?
  contract : contract?
  v : any/c

contract의 일차 테스트가 v에 대해 통과하는지 여부를 나타내는 불리언을 반환합니다.

#f를 반환하면, 계약이 그 값에 대해 성립하지 않음이 보장됩니다. #t를 반환하면, 계약이 성립할 수도 있고 아닐 수도 있습니다. 계약이 일차 계약이면, #t 결과는 계약이 성립함을 보장합니다.

contract-first-order-okay-to-give-up?contract-first-order-try-less-hard도 참고하세요.

procedure
(contract-first-order c) → (-> any/c boolean?)
  c : contract?

or/c가 값을 고차 계약에 매칭하는 데 사용하는 일차 테스트를 만듭니다.

더 알아보기