구조 타입 프로퍼티 계약
구조 타입 프로퍼티 계약 (Structure Type Property Contracts)
구조 타입 프로퍼티에 계약을 붙이는 방식과, 그 계약이 지켜지지 않을 때 누구를 탓하는지(blame)까지 살펴볼게요. struct-type-property/c가 어떤 흐름으로 동작하는지 전체 예시와 함께 설명해 드릴게요.
출처: Racket Reference
본문
procedure
(struct-type-property/c value-contract) → contract?
value-contract : contract?
구조 타입 프로퍼티에 대한 계약을 만들어 내요. 이 계약을 구조 타입 프로퍼티에 적용하면, 새 구조 타입을 만들 때(struct, make-struct-type 등으로) 그 프로퍼티에 연결된 값에 value-contract를 적용하는 래핑된 구조 타입 프로퍼티를 만들어 내요.
구조 타입 프로퍼티의 접근자 함수는 영향을 받지 않아요. 그것이 export된다면 별도로 보호해야 해요.
예를 들어 다음 모듈을 생각해 봐요. 이 모듈은 구조 인스턴스를 숫자 술어로 매핑하는 함수를 값으로 가져야 하는 구조 타입 프로퍼티 prop를 만들어요. 이 모듈은 또 구조 인스턴스에서 술어를 추출해 주어진 값에 적용하는 app-prop를 export해요.
> (module propmod racket
(require racket/contract)
(define-values (prop prop? prop-ref)
(make-struct-type-property 'prop))
(define (app-prop x v)
(((prop-ref x) x) v))
(provide/contract
[prop? (-> any/c boolean?)]
[prop (struct-type-property/c
(-> prop? (-> integer? boolean?)))]
[app-prop (-> prop? integer? boolean?)])
(provide prop-ref))
structmod 모듈은 단일 필드를 가진 이름이 s인 구조 타입을 만들어요. prop의 값은 인스턴스에서 필드 값을 추출하는 함수예요. 따라서 그 필드는 정수 술어여야 하지만, structmod가 s에 그 제약을 강제하는 계약을 두지 않는다는 점을 눈여겨보세요.
> (module structmod racket
(require 'propmod)
(struct s (f) #:property prop (lambda (s) (s-f s)))
(provide (struct-out s)))
> (require 'propmod 'structmod)
먼저 정수 술어로 s 인스턴스를 만들고, prop의 제약이 실제로 충족되는지 확인해 봐요. 첫 번째 app-prop 호출은 옳고, 두 번째는 단지 app-prop의 계약을 위반할 뿐이에요.
> (define s1 (s even?))
> (app-prop s1 5)
#f
> (app-prop s1 'apple)
app-prop: contract violation
expected: integer?
given: 'apple
in: the 2nd argument of
(-> prop? integer? boolean?)
contract from: propmod
blaming: top-level
(assuming the contract is correct)
at: eval:2:0
정수 술어가 아닌 값으로도 s 인스턴스를 만들 수 있어요. 하지만 그 위에서 app-prop를 적용하면 prop에 연결된 함수, 즉 (lambda (s) (s-f s))가 항상 (-> integer? boolean?)을 만족하는 값을 만들어 내지는 않기 때문에 structmod를 탓해요.
> (define s2 (s "not a fun"))
> (app-prop s2 5)
prop: contract violation
expected: a procedure
given: "not a fun"
in: the range of
the struct property value of
(struct-type-property/c
(-> prop? (-> integer? boolean?)))
contract from: propmod
blaming: structmod
(assuming the contract is correct)
at: eval:2:0
> (define s3 (s list))
> (app-prop s3 5)
prop: contract violation
expected: boolean?
given: '(5)
in: the range of
the range of
the struct property value of
(struct-type-property/c
(-> prop? (-> integer? boolean?)))
contract from: propmod
blaming: structmod
(assuming the contract is correct)
at: eval:2:0
해결책은 prop에서 물려받은 의무를 s로 전파하는 것이에요:
(provide (contract-out
[struct s ([f (-> integer? boolean?)])]))
마지막으로, 프로퍼티 접근자 prop-ref를 직접 적용한 뒤 결과 함수를 잘못 사용하면 propmod 모듈이 탓받아요:
> ((prop-ref s3) 'apple)
prop: broke its own contract
promised: prop?
produced: 'apple
in: the 1st argument of
the struct property value of
(struct-type-property/c
(-> prop? (-> integer? boolean?)))
contract from: propmod
blaming: propmod
(assuming the contract is correct)
at: eval:2:0
propmod 모듈은 prop에 연결된 함수가 prop?을 만족하는 값에만 적용되도록 보장할 의무가 있어요. prop-ref를 직접 제공함으로써 그 제약이 위반될 수 있게 만들었으므로(그래서 탓받아요), 실제 잘못된 적용이 다른 곳에서 일어났더라도 마찬가지예요.
일반적으로 구조 타입 프로퍼티 접근자를 제공할 필요는 전혀 없어요. 보통은 모듈 안의 다른 함수들만 그것을 사용해요. 하지만 제공해야 한다면 이렇게 보호해야 해요:
(provide (contract-out
[prop-ref (-> prop? (-> prop? (-> integer? boolean?)))]))