제네릭 인터페이스

제네릭 인터페이스 (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-idid?가 참인 값에 대한 대응하는 메서드를 호출하는 제네릭 메서드로.

method-idkw-formals*idfree-identifier=?인 필수 위치 인자를 포함해야 합니다. 그 인자는 제네릭 정의에서 특수화(specialization)를 찾는 데 사용됩니다.

  • id/cgen: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들의 구현을 공급해야 합니다. 그렇지 않으면 컴파일-시간 오류가 발생합니다.

vid?를 만족하면, vgen:id의 제네릭 인스턴스입니다.

제네릭 인스턴스 vstruct#:methodsdefine-generics#:defaults 또는 #:fast-defaults를 통해 제공된 어떤 method-id에 대한 대응 구현을 갖고 있으면, method-idv의 구현된 제네릭 메서드(implemented generic method)입니다.

method-id가 제네릭 인스턴스 v의 구현된 제네릭 메서드가 아니고, method-idv가 주어졌을 때 exn:fail:support 예외를 발생시키지 않는 폴백 구현을 갖고 있으면, method-idv의 지원되는 제네릭 메서드(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-idmethod-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) 관련 문서