제네릭 인터페이스
제네릭 인터페이스 (Generic Interfaces)
제네릭 인터페이스를 사용하면 타입별 메서드를 제네릭 함수에 연관시킬 수 있습니다. define-generics 폼으로 제네릭 함수를 정의하고, #:methods 키워드로 구조체 타입에 메서드 구현을 정의합니다.
출처: Racket Reference
본문
(require racket/generic) ; package: base
제네릭 인터페이스는 타입별 메서드가 제네릭 함수에 연관되도록 합니다. 제네릭 함수는 define-generics 폼으로 정의됩니다. 구조체 타입에 대한 메서드 구현은 #:methods 키워드로 정의됩니다(Defining Structure Types: struct 참고).
syntax
(define-generics id
generics-opt ...
[method-id . kw-formals*] ...
generics-opt ...)
generics-opt = #:defaults ([default-pred? default-impl ...] ...)
| #:fast-defaults ([fast-pred? fast-impl ...] ...)
| #:fallbacks [fallback-impl ...]
| #:defined-predicate defined-pred-id
| #:defined-table defined-table-id
| #:derive-property prop-expr prop-value-expr
| #:requires [required-method-id ...]
kw-formals* = (arg* ...)
| (arg* ...+ . rest-id)
| rest-id
arg* = arg-id
| [arg-id]
| keyword arg-id
| keyword [arg-id]
다음 이름들을 정의합니다(키워드 옵션이 지정한 이름은 추가로).
gen:id를 새 제네릭 인터페이스에 관한 정적 정보를 위한 트랜스포머 바인딩으로;id?를 이 제네릭 그룹을 구현하는 구조체 타입의 인스턴스들을 식별하는 술어로;- 각
method-id를id?가 참인 값에 대한 대응하는 메서드를 호출하는 제네릭 메서드로.
각 method-id의 kw-formals*는 id와 free-identifier=?인 필수 위치 인자를 포함해야 합니다. 그 인자는 제네릭 정의에서 특수화(specialization)를 찾는 데 사용됩니다.
id/c를gen:id제네릭 인터페이스를 구현하는 구조체 타입의 인스턴스들을 인식하는 계약 조합자(contract combinator)로. 이 조합자는 method-id와 계약의 쌍들을 받습니다. 계약들은 각 대응하는 메서드 구현에 적용됩니다.id/c조합자는 제네릭 인터페이스를 구현하는 struct 타입의 생성자 절차의 범위를 계약하는 데 사용되도록 의도되었습니다.
#:defaults 옵션은 기껏해야 한 번 제공될 수 있습니다. 제공되면, 각 제네릭 함수는 제네릭 메서드 테이블로의 디스패치가 실패할 경우 default-pred?들을 사용해 주어진 기본 메서드 구현 default-impl들로 디스패치합니다. default-impl들의 구문은 struct의 #:methods 키워드에 제공되는 메서드들과 같습니다.
#:fast-defaults 옵션은 기껏해야 한 번 제공될 수 있습니다. #:defaults와 같이 작동하지만, fast-pred?들이 제네릭 메서드 테이블로 디스패치하기 전에 검사됩니다. 이 옵션은 gen:id를 구현하는 구조체들과 겹치지 않는 내장 데이터 타입(리스트, 벡터 등)으로 디스패치하는 빠른 경로를 제공하도록 의도되었습니다.
#:fallbacks 옵션은 기껏해야 한 번 제공될 수 있습니다. 제공되면, fallback-impl들이, 특정 구현을 공급하지 않는 제네릭 인터페이스의 어떤 인스턴스에도 사용되는 폴백(fallback) 메서드 구현들을 정의합니다. fallback-impl들의 구문은 struct의 #:methods 키워드에 제공되는 메서드들과 같습니다.
#:defined-predicate 옵션은 기껏해야 한 번 제공될 수 있습니다. 제공되면, defined-pred-id는 제네릭 인터페이스의 특정 인스턴스가 주어진 메서드 집합을 구현하는지 보고하는 절차로 정의됩니다. 구체적으로, (defined-pred-id v 'name ...)는 v가 각 메서드 이름에 대한 구현(#:fallbacks 구현은 세지 않음)을 갖고 있으면 #t를 만들고, 그렇지 않으면 #f를 만듭니다. 이 절차는 상위 수준 API가 메서드 사용 가능성에 따라 동작을 조정하도록 의도되었습니다.
#:defined-table 옵션은 기껏해야 한 번 제공될 수 있습니다. 제공되면, defined-table-id는 제네릭 인터페이스의 인스턴스를 받아 메서드 이름에 해당하는 기호들을 인스턴스가 그 메서드를 구현하는지 여부를 나타내는 불리언들로 매핑하는 불변 해시 테이블을 반환하는 절차로 정의됩니다. 이 옵션은 폐기(deprecated)되었습니다. 대신 #:defined-predicate를 사용하세요.
#:derive-property 옵션은 여러 번 제공될 수 있습니다. 제공될 때마다 prop-expr로 구조체 타입 속성을, prop-value-expr로 그 속성에 대한 값을 지정합니다. #:methods를 통해 제네릭 인터페이스를 구현하는 모든 구조체는 제공된 값들을 사용해 이 구조체 타입 속성을 자동으로 구현합니다. prop-value-expr이 실행될 때, 각 method-id는 구조체 타입에 대한 그 특정 구현에 바인딩됩니다.
#:requires 옵션은 기껏해야 한 번 제공될 수 있습니다. 제공되면, 제네릭 인터페이스의 어떤 인스턴스든 지정된 required-method-id들의 구현을 공급해야 합니다. 그렇지 않으면 컴파일-시간 오류가 발생합니다.
값 v가 id?를 만족하면, v는 gen:id의 제네릭 인스턴스입니다.
제네릭 인스턴스 v가 struct의 #:methods나 define-generics의 #:defaults 또는 #:fast-defaults를 통해 제공된 어떤 method-id에 대한 대응 구현을 갖고 있으면, method-id는 v의 구현된 제네릭 메서드(implemented generic method)입니다.
method-id가 제네릭 인스턴스 v의 구현된 제네릭 메서드가 아니고, method-id가 v가 주어졌을 때 exn:fail:support 예외를 발생시키지 않는 폴백 구현을 갖고 있으면, method-id는 v의 지원되는 제네릭 메서드(supported generic method)입니다.
base 패키지의 8.7.0.5 버전에서 변경됨: #:requires 옵션 추가.
procedure
(raise-support-error name v) → none/c
name : symbol?
v : any/c
제네릭 인스턴스 v를 지원하지 않는 name이라는 제네릭 메서드에 대해 exn:fail:support 예외를 발생시킵니다.
예시:
> (raise-support-error 'some-method-name '("arbitrary" "instance" "value"))
some-method-name: not implemented for '("arbitrary"
"instance" "value")
struct
(struct exn:fail:support exn:fail ()
#:transparent)
주어진 제네릭 인스턴스를 지원하지 않는 제네릭 메서드에 대해 발생됩니다.
syntax
(define/generic local-id method-id)
#:methods, #:fallbacks, #:defaults, #:fast-defaults 키워드와 연관된 메서드 정의 안에서 사용되면, local-id를 method-id에 대한 제네릭으로 바인딩합니다. 이 폼은 메서드 특수화가 다른 값들에 대해 (지역 특수화가 아닌) 제네릭 메서드를 사용하는 데 유용합니다.
define/generic 폼은 다음 안에서만 허용됩니다:
struct(또는define-struct)의#:methods명세define-generics의#:fallbacks,#:defaults또는#:fast-defaults명세
다른 곳에서 define/generic을 사용하는 것은 문법 오류입니다.
예시:
> (define-generics printable
(gen-print printable [port])
(gen-port-print port printable)
(gen-print* printable [port] #:width width #:height [height])
#:defaults ([string?
(define/generic super-print gen-print)
(define (gen-print s [port (current-output-port)])
(fprintf port "String: ~a" s))
(define (gen-port-print port s)
; we can call gen-print alternatively
(super-print s port))
(define (gen-print* s [port (current-output-port)]
#:width w #:height [h 0])
(fprintf port "String (~ax~a): ~a" w h s))]))
> (struct num (v)
#:methods gen:printable
[(define (gen-print n [port (current-output-port)])
(fprintf port "Num: ~a" (num-v n)))
(define (gen-port-print port n)
(gen-print n port))
(define (gen-print* n [port (current-output-port)]
#:width w #:height [h 0])
(fprintf port "Num (~ax~a): ~a" w h (num-v n)))])
> (struct string+num (v n)
#:methods gen:printable
[(define/generic super-print gen-print)
(define/generic super-print* gen-print*)
(define (gen-print b [port (current-output-port)])
(super-print (string+num-v b) port)
(fprintf port " ")
(super-print (string+num-n b) port))
(define (gen-port-print port b)
(gen-print b port))
(define (gen-print* b [port (current-output-port)]
#:width w #:height [h 0])
(super-print* (string+num-v b) #:width w #:height h)
(fprintf port " ")
(super-print* (string+num-n b) #:width w #:height h))])
> (define x (num 10))
> (gen-print x)
Num: 10
> (gen-port-print (current-output-port) x)
Num: 10
> (gen-print* x #:width 100 #:height 90)
Num (100x90): 10
> (define str "Strings are printable too!")
> (gen-print str)
String: Strings are printable too!
> (define y (string+num str x))
> (gen-print y)
String: Strings are printable too! Num: 10
> (gen-port-print (current-output-port) y)
String: Strings are printable too! Num: 10
> (gen-print* y #:width 100 #:height 90)
String (100x90): Strings are printable too! Num (100x90): 10
> (define/contract make-num-contracted
(-> number?
(printable/c
[gen-print (->* (printable?) (output-port?) void?)]
[gen-port-print (-> output-port? printable? void?)]
[gen-print* (->* (printable? #:width exact-nonnegative-integer?)
(output-port? #:height exact-nonnegative-integer?)
void?)]))
num)
> (define z (make-num-contracted 10))
> (gen-print* z #:width "not a number" #:height 5)
make-num-contracted: contract violation
expected: natural?
given: "not a number"
in: the #:width argument of
method gen-print*
the range of
(->
number?
(printable/c
(gen-print
(->* (printable?) (output-port?) void?))
(gen-port-print
(-> output-port? printable? void?))
(gen-print*
(->*
(printable? #:width natural?)
(output-port? #:height natural?)
void?))))
contract from:
(definition make-num-contracted)
blaming: top-level
(assuming the contract is correct)
at: eval:16:0
syntax
(generic-instance/c gen-id [method-id method-ctc] ...)
method-ctc : contract?
제네릭 인터페이스 gen-id를 구현하는 구조체를 인식하고, 지정된 method-id들의 구현을 대응하는 method-ctc들로 제약하는 계약을 만듭니다.
syntax
(impersonate-generics gen-id val-expr
[method-id method-proc-expr] ...
maybe-properties)
maybe-properties =
| #:properties props-expr
method-proc-expr : (any/c . -> . any/c)
props-expr : (list/c impersonator-property? any/c ... ...)
제네릭 인터페이스 gen-id를 구현하는 구조체여야 하는 val-expr의 임퍼서네이터(impersonator)를 만듭니다. 임퍼서네이터는 method-proc-expr들의 결과를 구조체의 대응하는 method-id 구현에 적용하고, 메서드 구현을 그 결과로 대체합니다.
props-expr는 임퍼서네이터에 붙일 속성을 제공할 수 있습니다. props-expr의 결과는 짝수 개의 요소로 이루어진 리스트여야 하는데, 리스트의 첫 번째 요소는 임퍼서네이터 속성, 두 번째 요소는 그 값, 이렇게 계속됩니다.
base 패키지의 6.1.1.8 버전에서 변경됨: #:properties 추가.
syntax
(chaperone-generics gen-id val-expr
[method-id method-proc-expr] ...
maybe-properties)
impersonate-generics와 같지만, 제네릭 인터페이스 gen-id를 구현하는 구조체여야 하는 val-expr의 채퍼론(chaperone)을 만듭니다. 채퍼론은 지정된 method-proc들을 구조체의 대응하는 method-id 구현에 적용하고, 메서드 구현을 (원본의 채퍼론이어야 하는) 결과로 대체합니다.
syntax
(redirect-generics mode gen-id val-expr
[method-id method-proc-expr] ...
maybe-properties)
impersonate-generics와 같지만, mode가 #f로 평가되면 val-expr의 임퍼서네이터를 만들고, 그렇지 않으면 val-expr의 채퍼론을 만듭니다.
syntax
(make-struct-type-property/generic
name-expr
maybe-guard-expr
maybe-supers-expr
maybe-can-impersonate?-expr
property-option
...)
maybe-guard-expr =
| guard-expr
maybe-supers-expr =
| supers-expr
maybe-can-impersonate?-expr =
| can-impersonate?-expr
property-option = #:property prop-expr val-expr
| #:methods gen:name-id method-defs
method-defs = (definition ...)
name-expr : symbol?
guard-expr : (or/c procedure? #f 'can-impersonate)
supers-expr : (listof (cons/c struct-type-property? (-> any/c any/c)))
can-impersonate?-expr : any/c
prop-expr : struct-type-property?
val-expr : any/c
make-struct-type-property처럼 새 구조체 타입 속성을 만들고 세 값을 반환합니다:
- 구조체 타입 속성 디스크립터
- 속성 술어 절차
- 속성 접근자 절차
이 속성을 구현하는 모든 struct는 #:property와 #:methods 선언에 주어진 속성들과 제네릭 인터페이스들도 구현합니다. 속성 val-expr들과 method-def들은 속성이 만들어질 때(구조체 타입에 붙을 때가 아니라) 즉시(urgently) 평가됩니다.
syntax
(make-generic-struct-type-property
gen:name-id
method-def
...)
새 구조체 타입 속성을 만들고 구조체 타입 속성 디스크립터를 반환합니다.
이 속성을 구현하는 모든 struct는 주어진 method-def들로 gen:name-id가 주는 제네릭 인터페이스도 구현합니다. method-def들은 속성이 만들어질 때(구조체 타입에 붙을 때가 아니라) 즉시 평가됩니다.
더 알아보기
- Defining Structure Types: struct 문서
make-struct-type-property관련 문서- 채퍼론과 임퍼서네이터(Chaperones and Impersonators) 관련 문서