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.
(new (deal-with-mouse (deal-with-key world-editor-canvas%)) ...)
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.
(define world-editor-canvas% (class editor-canvas% (super-new)))
; (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)))
Use the complete code for the mixin-based world to see for yourself how first-class classes and mixins work.