如何覆盖 class 中字段的默认值?

How do you override the default value of a field in a class?

这是迄今为止我发现的覆盖 class:

中默认字段值的最佳方法
(define sa-canvas%
  (class canvas%
    . . .
    (init-field [min-width 600]
                [min-height 300]
                [stretchable-width #f]
                [stretchable-height #f])
    (super-new [min-width min-width]
               [min-height min-height]
               [stretchable-width stretchable-width]
               [stretchable-height stretchable-height])
    . . .))

这对于 Racket 来说似乎太冗长了。 Racket 的 class 系统旨在支持的预期方式是什么?

首先,您的示例做了您可能不想做的事情。使用 init-field 在 class 的实例上创建一个可变的 public 字段,该字段已初始化为相应的初始化参数。这些并不经常使用:更常见的是使用私有字段(即 define)和 getter 方法,如果你想支持突变,可能使用 setter。事实上,我们可以看出,但是 canvas% 使用这些初始化参数,它不会将它们存储为 public 字段,因为(与方法一样)class 不能创建与其 superclass 的字段具有相同外部名称的新字段。 (字段可以用 inherit-field 继承。)例如,计算这个表达式会引发异常:

(class (class object%
         (init-field [x 1])
         (super-new))
  (init-field [x 2])
  (super-new [x x]))

对于您不想作为字段保留的初始化参数,请使用 init 而不是 init-field。此表达式与您的示例相同,但不创建字段:

(class canvas%
  (init [min-width 600]
        [min-height 300]
        [stretchable-width #f]
        [stretchable-height #f])
  (super-new [min-width min-width]
             [min-height min-height]
             [stretchable-width stretchable-width]
             [stretchable-height stretchable-height]))

不过,您是对的,要保持同步需要大量输入和繁琐的代码位。 class 库中没有内置不同的方法,但您可以使用一个小宏轻松地自己添加一个:

(class canvas%
  (define-syntax-rule (init/super-new [name default] ...)
    (begin (init [name default] ...)
           (super-new [name name] ...)))
  (init/super-new
   [min-width 600]
   [min-height 300]
   [stretchable-width #f]
   [stretchable-height #f]))

define-syntax-rule 可以很容易地在模块级别消失,但是:

  1. 本地宏很酷;和

  2. 宏的一大优点是您不必处理一般情况:比如说,也许有时您还想将其他参数传递给 super-new。如果你愿意,你当然可以支持它,但是,有了宏,你和图书馆的作者都不必提前预见到你可能想要的每一种可能的便利。编写一个简单的宏来使您的特定代码更具可读性是完全合法的。