4 Classes in Action

The world-editor-canvas% class of figure 7 introduces a performance bottleneck into the teaching library. Students discovered this problem a couple of years after the release of the library. Specifically, students wrote programs without, say, specifying a mouse handler, and yet these programs suffered from the mere presence of the default on-event method (for mouse events) in the implementations.

The current version of the Racket code base exploits mixin methods to solve this performance bug. Figure 9 displays the two mixin methods, both of which belong to the (private part of the) world% class defined in figure 2.

; EditorCanvas% -> EditorCanvas% possibly with on-char
(define/private (deal-with-key %)
  (if (not on-key)
      %
      (class %
        (super-new)
        ; (instance-of Key-event%) -> State
        ; compute new state in reaction to key event
        (define/override (on-char e)
          (define state (send visible get))
          (define next (on-key state (key-event->parts e)))
          (send visible update! next)))))
 
; EditorCanvas% -> EditorCanvas% possibly with on-event
(define/private (deal-with-mouse %)
  (if (not on-mouse)
      %
      (class %
        (super-new)
        ; (instance-of Mouse-event%) -> State
        ; compute new state in reaction to mouse event
        (define/override (on-event e)
          (define state (send visible get))
          (define-values (x y me) (mouse-event->parts e))
          (when (good-mouse? x y me)
            (define next (on-mouse state x y me))
            (send visible update! next)))
 
        (define/private (good-mouse? x y me)
          (or (and (<= 0 x width) (<= 0 y height))
              (member me '("leave" "enter")))))))

Figure 9: Methods that programmatically extend classes, part of world%

The two mixin methods use the same organization. Each consumes a base class. Each tests the value of a field that corresponds to its purpose. While the deal-with-key method tests the on-key field, the deal-with-mouse mixin checks the on-mouse field. If the tested values are false, the mixin methods return the given class; otherwise they extend the given class so that the newly created subclass overrides the appropriate event-handling method in the base class. The deal-with-key mixin overrides the on-char method so that key events are processed according to the specifications of the on-key clause of the big-bang program. Similarly, the deal-with-mouse method overrides the on-event method so that the big-bang specified on-mouse handler takes over; a private method checks whether the mouse click is inside the displayed canvas.

Once world% is equipped with the two mixin methods, it is possible to create the world’s canvas with an application of the two methods:

(new (deal-with-mouse (deal-with-key world-editor-canvas%)) ...)

The invocation of deal-with-key consumes world-editor-canvas% class and returns either the class itself or an extension. In the same vein, the call to the deal-with-mouse mixin may derive an extended class from the result of the inner call—or not. In sum, the application of the two mixin methods may have one of four possible outcomes:
  • the given canvas class,

  • a class extension with a method for dealing with key events,

  • a class extension with a method for dealing with mouse events, or

  • a class extension with one method for dealing with key events and another one for mouse events.

An alternative to mixins is to use a four-armed conditional that creates the desired class depending on which combination of on-key and on-mouse holds. Clearly, such a conditional is more complex than a simple linear application of two mixin methods, and it is less extensible. If the big-bang library is ever equipped with another optional event handler like those for key and mouse events, the mixin chain can be extended with one application while the conditional would have to deal with many new cases.

The introduction of these mixin methods also shifts the mouse and key event functionality from world-editor-canvas% to world%. Indeed, the former class definition becomes trivial:
(define world-editor-canvas%
  (class editor-canvas%
    (super-new)))
meaning the class is behaviorally identical to editor-canvas%. Hence, the definition of editor-canvas in world% turns into the immediate instantiation of a double-mixin class, as shown in figure 10.

; (instance-of World-editor-canvas%)
(define editor-canvas
  (new (deal-with-mouse (deal-with-key editor-canvas%))
       (parent frame)
       (editor visible)
       (stretchable-width #f)
       (stretchable-height #f)
       (style '(no-hscroll no-vscroll))
       (horizontal-inset INSET)
       (vertical-inset INSET)))

Figure 10: The revised editor-canvas definition of world%

Use the complete code for the mixin-based world to see for yourself how first-class classes and mixins work.