Другой ответ уже помог вам найти проблему в вашем коде - вам нужно использовать порт, указанный в качестве аргумента, а не неявный (current-output-port)
, но объяснение не совсем верно. Чтобы ответить на ваши вопросы в обратном порядке:
- Правильно ли это использовать для этой цели? Если нет, есть ли один / что это?
Да, printable<%>
- это правильная конструкция, используемая для настройки печати объекта на основе классов. В более общем смысле тип структуры, который не является классом, может настраивать печать, например, через универсальный интерфейс gen:custom-write
или низкоуровневое свойство типа структуры prop:custom-write
, которое используется для реализации printable<%>
.
- Что должны оценивать методы custom-print, custom-write и custom-display? Должны ли они просто возвращать строку или они могут повлечь за собой побочный эффект печати, записи или отображения, в зависимости от обстоятельств? Например. должен ли метод custom-write вызвать функцию write для внутреннего использования?
Методы на самом деле должны вызывать побочный эффект выполнения ввода-вывода для порта, который они задают в качестве аргумента. Они должны использовать соответствующую функцию (например, write
для custom-write
, print
для custom-print
) внутри для рекурсивной печати / записи / отображения значений в полях. С другой стороны, при непосредственном выделении определенных символов они обычно должны использовать такие функции, как write-char
, write-string
или printf
. Документы для gen:custom-write
дают пример типа данных кортежа, который печатается как <1, 2, "a">
: он использует write-string
для угловых скобок и запятых, но рекурсивно print
/ write
/ display
для элементов кортежа.
- Почему он ведёт себя так, т. Е. С множеством методов, вызываемых при явно уникальном рендеринге объекта?
Это самая сложная часть вашего вопроса. Печать в Racket настраивается через несколько зацепок: несколько примеров см. current-print
, port-write-handler
, global-port-print-handler
и make-tentative-pretty-print-output-port
. Многие из этих хуков настройки используют промежуточные порты в процессе вывода.
Одна вещь, которая не является частью объяснения, заключается в том, что вы использовали print
в своей реализации, особенно когда print
привязан к нормальной функции Racket с помощью лексической области видимости, а не метод вашего объекта.
В качестве иллюстрации рассмотрим следующую адаптацию вашего примера, которая сообщает (current-output-port)
идентификатор порта, указанный в качестве аргумента метода:
#lang racket
(define report
(let ([next-id 0]
[id-cache (make-hash)])
(λ (op port)
(printf "~a ~a ~v\n"
op
(hash-ref id-cache
port
(λ ()
(define id next-id)
(hash-set! id-cache port id)
(set! next-id (add1 next-id))
id))
port))))
(define fish%
(class* object% (printable<%>)
(super-new)
;; implement printable interface
(define/public (custom-print port quoting-depth)
(report "custom-print " port))
(define/public (custom-write port)
(report "custom-write " port))
(define/public (custom-display port)
(report "custom-display" port))))
(define f (new fish%))
f
(print f)
(newline)
(display f)
(newline)
(write f)
В DrRacket это генерирует вывод:
custom-display 0 #<output-port:null>
custom-display 1 #<output-port:null>
custom-print 2 #<printing-port>
custom-display 3 #<output-port:null>
custom-display 4 #<output-port:null>
custom-print 5 #<printing-port>
custom-display 6 #<output-port:null>
custom-display 7 #<printing-port>
custom-display 8 #<output-port:null>
custom-write 9 #<printing-port>
в то время как в командной строке вывод:
$ racket demo.rkt
custom-write 0 #<output-port:null>
custom-print 1 #<output-port:redirect>
custom-write 2 #<output-port:null>
custom-print 3 #<output-port:redirect>
custom-display 4 #<output-port:null>
custom-display 5 #<output-port:redirect>
custom-write 6 #<output-port:null>
custom-write 7 #<output-port:redirect>