프린터 확장

프린터 확장 (Printer Extension)

구조 타입이 프린트될 방식을 커스터마이즈하는 방법을 살펴볼게요. gen:custom-write 제네릭 인터페이스와 관련 프로퍼티를 이용해 write·display·print 모드 각각에서 내 입맛대로 출력을 다룰 수 있어요.

출처: Racket Reference

본문

value

gen:custom-write : any/c

기본 프린터가 구조 타입의 인스턴스를 display·write·print할 때 사용하는 메서드 write-proc를 제공하는 제네릭 인터페이스예요(Generic Interfaces 참고).

write-proc 메서드는 세 개의 인자를 받아요: 프린트할 구조체, 대상 포트, 그리고 write 모드에서는 #t, display 모드에서는 #f, print 모드에서는 현재 인용 깊이를 나타내는 0 또는 1인 인자예요. 프로시저는 write, display, print, fprintf, write-special 등을 사용해 값을 주어진 포트에 프린트해야 해요.

커스텀 write 프로시저에 주어진 포트에 대해서는 포트 write 핸들러, 포트 display 핸들러, print 핸들러가 특별히 설정돼요. 그 포트에 display, write, print를 통해 프린트하면 공유 주석(annotation)과 함께 값을 재귀적으로 프린트해요. 재귀적 프린트를 피하려면(즉 현재 프린트 중인 값과의 공유를 고려하지 않고 프린트하려면) 대신 문자열이나 파이프에 프린트한 뒤 결과를 write-string이나 write-special로 대상 포트에 옮기면 돼요. 커스텀 write 프로시저에 주어진 것과 다른 포트에 재귀적으로 프린트하려면, 주어진 포트의 write 핸들러·display 핸들러·print 핸들러를 다른 포트에 복사하세요.

write-proc에 주어지는 포트는 실제 대상 포트가 아닐 수 있어요. 특히 순환·공유·인용 모드를 감지하기 위해(print의 경우) 프린터는 재귀적 프린트에 대한 정보를 기록하고 다른 출력은 보관하지 않는 포트로 커스텀 write 프로시저를 호출해요. 이 정보 수집 단계에서는 나중에 (기록된 정보를 프린트된 값과 연관 지을 수 있도록) 같은 객체를 eq? 의미로 프린트할 필요가 있어요.

재귀적 print 연산은 write-proc 호출에서 탈출(escape)을 유발할 수 있어요. 예를 들어, 시도된 print가 줄을 넘치는 pretty-printing 중에 프린트가 탈출하거나, 제한된 폭에 갇힌 오류 출력을 프린트하는 동안 탈출할 수 있어요.

튜플 타입의 다음 예시 정의에는 write·print 모드에서는 각진 괄호로, display 모드에서는 괄호 없이 튜플의 리스트 내용을 프린트하는 write-proc 프로시저가 포함돼 있어요. 튜플의 요소들은 재귀적으로 프린트되므로, 그래프와 순환 구조를 표현할 수 있어요.

예시를 볼게요:

(define (tuple-print tuple port mode)
  (when mode (write-string "<" port))
  (let ([l (tuple-ref tuple)]
        [recur (case mode
                 [(#t) write]
                 [(#f) display]
                 [else (lambda (p port) (print p port mode))])])
    (unless (zero? (vector-length l))
      (recur (vector-ref l 0) port)
      (for-each (lambda (e)
                  (write-string ", " port)
                  (recur e port))
                (cdr (vector->list l)))))
  (when mode (write-string ">" port)))

(struct tuple (ref)
        #:methods gen:custom-write
        [(define write-proc tuple-print)])

> (display (tuple #(1 2 "a")))
1, 2, a
> (print (tuple #(1 2 "a")))
<1, 2, "a">
> (let ([t (tuple (vector 1 2 "a"))])
    (vector-set! (tuple-ref t) 0 t)
    (write t))
#0=<#0#, 2, "a">

make-constructor-style-printer 함수가 다음 예시처럼 write-proc의 구현을 도와줄 수 있어요:

예시를 볼게요:

(require racket/struct)

(struct point (x y)
  #:methods gen:custom-write
  [(define write-proc
     (make-constructor-style-printer
      (lambda (obj) 'point)
      (lambda (obj) (list (point-x obj) (point-y obj)))))])

> (print (point 1 2))
(point 1 2)
> (write (point 1 2))
#<point: 1 2>

패키지 base의 버전 8.7.0.5에서 변경됨: write-proc를 생략하는 것이 이제 구문 오류가 되도록 검사가 추가됐어요.

value

prop:custom-write : struct-type-property?

gen:custom-writewrite-proc에 대응하는 프로시저를 제공하는 구조 타입 프로퍼티예요(Structure Type Properties 참고). prop:custom-write 프로퍼티를 쓰는 것은 권장되지 않아요. 대신 gen:custom-write 제네릭 인터페이스를 쓰세요.

procedure

(custom-write? v) → boolean?
  v : any/c

vprop:custom-write 프로퍼티를 가지면 #t를, 그렇지 않으면 #f를 돌려줘요.

procedure

(custom-write-accessor v)
 → (custom-write? output-port? (or/c #t #f 0 1) . -> . any)
  v : custom-write?

v에 연결된 커스텀 write 프로시저를 돌려줘요.

value

prop:custom-print-quotable : struct-type-property?

value

custom-print-quotable? : struct-type-property?

value

custom-print-quotable-accessor : struct-type-property?

프로퍼티와 그에 연결된 술어·접근자예요. 프로퍼티 값은 'self, 'never, 'maybe, 'always 중 하나예요. 구조가 prop:custom-write 프로퍼티 값 외에 이 프로퍼티도 가지면, 그 프로퍼티 값이 print 모드에서의 프린팅에 영향을 줘요. prop:custom-print-quotable 값을 가지지 않는 값은 'self 프로퍼티 값을 가지는 것과 동등한데, 이는 자기 인용(self-quoting) 폼과 읽을 수 없는(unreadable) 프린트된 폼 양쪽에 모두 적합해요.

더 알아보기