계약 유틸리티
계약 유틸리티
계약 그 자체의 동작을 조사하고 새 계약 결합자를 정의할 때 쓸 수 있는 여러 유틸리티를 소개해요. 계약이 값에 어떻게 적용되는지(프로젝션), 제1차(first-order) 검사가 언제 가능한지, 계약에 이름을 붙이고 최적화하는 방법 등을 다뤄요.
이 절은 racket/contract를 사용한다고 가정해요.
출처: Racket Reference
본문
procedure
(contract? v) → boolean?
v: any/c
인자가 계약이면(즉, 이 절에서 설명한 결합자 중 하나로 만들었거나 계약으로 사용할 수 있는 값이면) #t를, 그렇지 않으면 #f를 돌려줘요.
procedure
(chaperone-contract? v) → boolean?
v: any/c
인자가 체퍼론 계약(chaperone contract)이면, 즉 원래 계약되지 않은 값과 비교할 때 chaperone-of?를 통과하는 값을 돌려준다고 보장하는 계약이면 #t를 돌려줘요.
procedure
(impersonator-contract? v) → boolean?
v: any/c
인자가 임퍼서네이터 계약(impersonator contract)이면, 즉 체퍼론 계약도 플랫 계약도 아닌 계약이면 #t를 돌려줘요.
procedure
(flat-contract? v) → boolean?
v: any/c
인자가 즉시 검사할 수 있는 계약(예: 함수 계약과 달리)이면 #t를 돌려줘요.
예를 들어 flat-contract는 술어로부터 플랫 계약을 만들고, 심볼·불리언·숫자처럼 계약으로 정의된 일반적인 Racket 값들도 플랫 계약이에요.
procedure
(list-contract? v) → boolean?
v: any/c
리스트를 받아들이는 계약 값(in contract?) 중 일부를 인식해요.
리스트 계약은 그 인자가 list?라고 요구하는 계약으로, 값이 순환적이지 않아야 하고 빈 리스트이거나 cons와 다른 리스트로 만든 페어여야 해요.
procedure
(contract-name c) → any/c
c: contract?
오류 메시지에서 계약을 설명할 때 쓰는 이름을 만들어내요.
procedure
(value-contract v) → (or/c contract? #f)
v: has-contract?
기록되어 있으면 v에 붙어 있는 계약을 돌려주고, 그렇지 않으면 #f를 돌려줘요.
자신의 계약 결합자에서 value-contract을 지원하려면 prop:contracted나 impersonator-prop:contracted를 사용해요.
procedure
(has-contract? v) → boolean?
v: any/c
v가 기록된 계약이 붙어 있는 값이면 #t를 돌려줘요.
procedure
(value-blame v) → (or/c blame? #f)
v: has-blame?
기록되어 있으면 v에 붙어 있는 계약의 블레임(blame) 객체를 돌려주고, 그렇지 않으면 #f를 돌려줘요.
자신의 계약 결합자에서 value-blame을 지원하려면 prop:blame이나 impersonator-prop:blame을 사용해요.
procedure
(has-blame? v) → boolean?
v: any/c
v가 블레임 정보가 붙어 있는 계약을 가진 값이면 #t를 돌려줘요.
procedure
(contract-late-neg-projection c) → (-> blame? (-> any/c (or/c #f any/c) any/c))
c: contract?
계약의 동작을 정의하는 프로젝션(projection)을 만들어내요.
첫 번째 인자인 blame? 객체는 계약 검사에 대한 정보를 담고 있으며, 주로 계약 위반이 감지됐을 때 의미 있는 오류 메시지를 만드는 데 쓰여요. 결과 함수의 첫 번째 인자는 계약이 적용되어야 할 값이고, 두 번째 인자는 raise-contract-error에 전달할 블레임 객체의 부재당사자(missing party)예요.
가능하면 이 함수를 contract-val-first-projection이나 contract-projection 대신 사용해요.
procedure
(contract-projection c) → (-> blame? (-> any/c any/c))
c: contract?
계약의 동작을 정의하는 프로젝션을 만들어내요. 이 프로젝션은 두 인자를 받는 커리된 함수로, 첫 번째 적용은 블레임 객체를 받고 두 번째 적용은 계약으로 보호할 값을 받아요.
가능하면 contract-late-neg-projection을 대신 사용해요.
procedure
(contract-val-first-projection c) → (-> blame? (-> any/c (-> any/c any/c)))
c: contract?
계약의 동작을 정의하는 프로젝션을 만들어내요. 이 프로젝션은 커링의 층이 하나 더 있다는 점만 빼고 contract-late-neg-projection의 결과와 비슷해요.
가능하면 contract-late-neg-projection을 대신 사용해요.
procedure
(make-none/c sexp-name) → contract?
sexp-name: any/c
어떤 값도 받아들이지 않는 계약을 만들고, 계약 위반을 알릴 때 sexp-name을 이름으로 보고해요.
syntax recursive-contract
(recursive-contract contract-expr recursive-contract-option ...)
(recursive-contract contract-expr type recursive-contract-option ...)
recursive-contract-option=#:list-contract?|#:extra-delaytype=#:impersonator|#:chaperone|#:flat
인자의 평가를 계약이 검사될 때까지 늦춰서 재귀 계약을 가능하게 해요. type이 주어지지 않으면 임퍼서네이터 계약이 만들어져요.
#:list-contract? 옵션이 주어지면 결과는 list-contract?가 되고, contract-expr은 list-contract?로 평가되어야 해요.
#:extra-delay 옵션이 주어지면, contract-expr은 계약에 대항해 검사할 첫 번째 값이 계약에 공급될 때에만 평가돼요. 이 옵션이 없으면 contract-expr은 더 일찍 평가돼요. 이 옵션은 type이 #:flat일 때만 지원돼요.
예시:
> (define even-length-list/c
(or/c null?
(cons/c any/c
(cons/c any/c
(recursive-contract even-length-list/c #:flat)))))
> (even-length-list/c '(A B))
#t
> (even-length-list/c '(1 2 3))
#f
syntax opt/c
(opt/c contract-expr maybe-name)
maybe-name= `` |#:error-name id
인자 계약 표현식의 문법을 탐색해서, 알려진 계약 결합자들을 가능한 한 많은 할당 오버헤드를 피하는 단일 계약 결합자로 융합하는 방식으로 그 표현식을 최적화해요. 결과는 더 빠르다는 점만 빼고 인자와 동일하게 동작해야 하는 계약이에요.
#:error-name 인자가 있고, contract-expr이 계약이 아닌 표현식으로 평가되면, opt/c는 opt/c라는 이름 대신 id를 프리미티브 이름으로 사용해 오류를 발생시켜요.
예시:
> (define/contract (f x)
(opt/c '(not-a-contract))
x)
opt/c: contract violation
expected: contract?
given: '(not-a-contract)
> (define/contract (f x)
(opt/c '(not-a-contract) #:error-name define/contract)
x)
define/contract: contract violation
expected: contract?
given: '(not-a-contract)
syntax define-opt/c
(define-opt/c (id id ...) expr)
재귀 계약을 정의하면서 동시에 최적화해요. 정의된 함수가 종료되는 한, define-opt/c는 -opt/c가 없을 때처럼 동작하며(본문 표현식은 계약을 돌려줘야 한다는 점만 제외) opt/c처럼 이 계약 정의를 최적화해서 추가 할당을 피해요.
예를 들어,
(define-contract-struct bt (val left right))
(define-opt/c (bst-between/c lo hi)
(or/c null?
(bt/c [val (real-in lo hi)]
[left (val) (bst-between/c lo val)]
[right (val) (bst-between/c val hi)])))
(define bst/c (bst-between/c -inf.0 +inf.0))
는 이진 탐색 트리 불변식을 검사하는 bst/c 계약을 정의해요. -opt/c를 제거해도 이진 탐색 트리 계약이 만들어지지만, (대략) 20배 더 느려요.
일부 경우에는 define-opt/c로 정의한 함수의 호출이 종료될 수 있는데, 대응하는 define 기반 함수는 종료되지 않을 수도 있어요. 이것은 define-opt/c의 단점이며, 언젠가 이해하고 고치길 희망하지만 현재 구체적인 계획은 없어요.
value
contract-continuation-mark-key : continuation-mark-key?
계약 검사 중에 존재하는 연속 마크가 쓰는 키예요. 이 마크들의 값은 현재 검사 중인 계약에 대응하는 블레임 객체들이에요.
procedure
(contract-custom-write-property-proc c p mode) → void?
c: contract?p: output-port?mode: (or/c #f #t 0 1)
계약의 이름을 사용해 c를 p에 출력해요.
procedure
(rename-contract contract name) → contract?
contract: contract?name: any/c
contract처럼 동작하지만 이름이 name인 계약을 만들어요. contract가 플랫 계약이면 결과도 플랫 계약이에요.
syntax contract-first-order-okay-to-give-up?
이 형식은 제1차(fist-order) 계약 검사의 결과를 제어하는 불리언을 돌려줘요. 구체적으로, 이 형식이 #t를 돌려주면, 전체 제1차 검사가 일어나지 않았더라도 제1차 검사가 #t를 돌려줄 수 있어요. #f를 돌려주면 제1차 검사는 확정적인 답이 나올 때까지 계속되어야 해요.
이 형식은 or/c나 first-or/c의 검사가 어떤 분기를 쓸지 결정하는 동안의 동적 범위 안에서만 #t를 돌려줘요.
syntax contract-first-order-try-less-hard
(contract-first-order-try-less-hard e)
e의 동적 범위 안에서 일어나는 제1차 검사가 포기할 가능성이 더 높아지도록 유도해요. 즉, contract-first-order-okay-to-give-up?가 #t를 돌려줄 가능성을 높여요.
or/c나 first-or/c의 검사가 분기를 판단하는 동안의 동적 범위 밖에서는 이 형식은 효과가 없어요.
procedure
(if/c predicate then-contract else-contract) → contract?
predicate: (-> any/c any/c)then-contract: contract?else-contract: contract?
값에 적용되면 먼저 predicate로 그 값을 검사하고, predicate가 참을 돌려주면 then-contract를 적용하고, 그렇지 않으면 else-contract를 적용하는 계약을 만들어내요. then-contract와 else-contract가 모두 플랫 계약이면 결과 계약도 플랫 계약이에요.
예를 들어 다음 계약은 값이 프로시저이면 thunk이도록 강제하고, 그렇지 않으면 (프로시저가 아닌) 어떤 값이든 허용해요: (if/c procedure? (-> any) any/c)
다음 계약은 동일하지 않다는 점에 주의해요: (or/c (-> any) any/c) ; wrong! 마지막 계약은 any/c와 같은데, or/c가 고차 계약보다 플랫 계약을 먼저 시도하기 때문이에요.
value
failure-result/c : contract?
hash-ref 같은 프로시저의 실패 결과 인자들을 설명하는 계약이에요.
(if/c procedure? (-> any) any/c)와 동일해요.
procedure
(get/build-val-first-projection c) → (-> blame? (-> any/c (-> any/c any/c)))
c: contract?
c의 val-first 프로젝션을 돌려줘요. 자세한 내용은 make-contract을 참고해요.
procedure
(get/build-late-neg-projection c) → (-> blame? (-> any/c any/c any/c))
c: contract?
c의 late-neg 프로젝션을 돌려줘요. c에 late-neg 계약이 없으면 이 함수는 원래 프로젝션을 사용하고 'racket/contract 로거에 경고를 기록해요. 자세한 내용은 make-contract을 참고해요.