Search code examples
classinheritancefieldracketdefault-value

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


This is the best way I've found so far to override default field values in a 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])
    . . .))

This seems way too verbose for Racket. What is the intended way that Racket's class system is designed to support?


Solution

  • First of all, your example does something you probably don't want. Using init-field creates a mutable, public field on instances of your class that is initialized to the corresponding initialization argument. These aren't used very often: it's more common to use a private field (i.e. define) and a getter method, perhaps with a setter if you want to support mutation. In fact, we can tell that, however canvas% uses those initialization arguments, it doesn't store them as public fields, because (as with methods) a class can't create a new field with the same external name as a field of its superclass. (Fields can be inherited with inherit-field.) For example, evaluating this expression raises an exception:

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

    For initialization arguments that you don't want to keep around as fields, use init instead of init-field. This expression is the same as your example, but doesn't create fields:

    (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]))
    

    You're right, though, that this is a lot of typing and fiddly bits of code to keep in sync. There isn't a different way built in to the class library, but you can easily add one yourself with a little macro:

    (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]))
    

    The define-syntax-rule could just as easily have gone at the module level, but:

    1. Local macros are cool; and

    2. One of the great things about macros is that you don't have to handle the general case: say, maybe sometimes you want to also pass other arguments to super-new. You could certainly support that if you wanted to, but, with macros, neither you nor the library writer has to foresee every possible convenience you might want in advance. It it totally legitimate to write a simple macro that just makes your specific code more readable.