3

在 python 对象中,重写对象的方法__repr__和方法__str__可以分别提供对象的“明确”和“人类可读”表示。如何在 Racket 中实现类似的行为?

我在这里遇到了printable<%>界面,看起来它应该可以用于此目的,但我无法让它像我期望的那样工作。基于文档中的标准“鱼”示例:

(define fish%
  (class* object% (printable<%>)
    (init size) ; initialization argument
    (super-new) ; superclass initialization
    ;; Field
    (define current-size size)

    ;; Public methods
    (define/public (get-size)
      current-size)
    (define/public (grow amt)
      (set! current-size (+ amt current-size)))
    (define/public (eat other-fish)
      (grow (send other-fish get-size)))

    ;; implement printable interface
    (define/public (custom-print port quoting-depth)
      (print "Print called!"))
    (define/public (custom-write port)
      (print "Write called!"))
    (define/public (custom-display port)
      (print "Display called!"))))

这是我得到的输出:

> (define f (new fish% [size 10]))
> f
"Display called!""Display called!""Print called!"
> (print f)
"Write called!""Print called!"
> (display f)
"Display called!""Display called!"
> (write f)
"Write called!""Write called!"
> 

所以问题是三方面的:

  1. 为什么它会以这种方式运行,即在对象的明显单一渲染上调用多个方法?

  2. custom-print、custom-write 和 custom-display 方法的计算结果是什么?他们应该只是简单地返回一个字符串,还是应该根据具体情况实际产生打印、写入或显示的副作用?例如,custom-write 方法是否应该在内部调用该write函数?

  3. 这是用于此目的的正确构造吗?如果没有,有没有/它是什么?

4

2 回答 2

2

另一个答案已经帮助您找到代码中的问题——您需要使用作为参数给出的端口,而不是隐式的(current-output-port)——但解释并不完全正确。要以相反的顺序解决您的问题:

  1. 这是用于此目的的正确构造吗?如果没有,有没有/它是什么?

是的,printable<%>是用于自定义打印基于类的对象的正确构造。更一般地说,不是类的结构类型可以自定义打印,例如通过gen:custom-write通用接口或用于实现的低级prop:custom-write结构类型属性printable<%>

  1. custom-print、custom-write 和 custom-display 方法的计算结果是什么?他们应该只是简单地返回一个字符串,还是应该根据具体情况实际产生打印、写入或显示的副作用?例如,custom-write 方法是否应该在内部调用 write 函数?

这些方法实际上应该在作为参数给出的端口上执行 IO 的副作用。他们应该在内部使用相应的函数(例如writefor custom-writeprintfor custom-print)来递归地打印/写入/显示字段中的值。另一方面,当直接发出特定字符时,它们通常应使用write-charwrite-string或等函数printf文档gen:custom-write给出了一个元组数据类型的示例,它打印为<1, 2, "a">:它write-string用于尖括号和逗号,但递归//print用于元组的元素。writedisplay

  1. 为什么它会以这种方式运行,即在对象的明显单一渲染上调用多个方法?

这是您的问题中涉及最多的部分。Racket 中的打印可通过几个钩子进行自定义:有关一些示例,请参见current-printport-write-handlerglobal-port-print-handlermake-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>
于 2019-06-25T18:19:53.990 回答
2

至于

  1. 为什么它会以这种方式运行,即在对象的明显单一渲染上调用多个方法?

你不小心用print了 in write,所以写值,会先打印值。

(define/public (custom-write port)
   (print "Write called!"))

类似的问题存在于display.

还要记住打印/写入/显示到正确的端口。

尝试

#lang racket
(define fish%
  (class* object% (printable<%>)
    (init size) ; initialization argument
    (super-new) ; superclass initialization
    ;; Field
    (define current-size size)

    ;; Public methods
    (define/public (get-size)
      current-size)
    (define/public (grow amt)
      (set! current-size (+ amt current-size)))
    (define/public (eat other-fish)
      (grow (send other-fish get-size)))

    ;; implement printable interface
    (define/public (custom-print port quoting-depth)
      (print (~a "Print " current-size "\n") port))
    (define/public (custom-write port)
      (write (~a "Write " current-size "\n") port))
    (define/public (custom-display port)
      (display (~a "Display " current-size "\n") port))))

在repl中你会看到:

> (define f (new fish% [size 10]))
> f
"Print 10\n"
> (display f)
Display 10
> (write f)
"Write 10\n"
于 2019-05-03T19:29:41.870 回答