On this page:
1 Functional GUIs
2 Classes in Racket
2.1 Of Classes and Objects
2.2 Class Extensions
3 Classes are Values
3.1 Operations on Classes
3.2 Mixins, a First Taste
3.3 Reflective Operations
4 Classes in Action
5 Dr  Racket:   Start Me Up
6.6.0.3

Programming with Class in Racket

Matthias Felleisen, Robert Bruce Findler

While Racket originated in the functional world of programming languages, it also is a full-fledged class-based, object-oriented programming language. Indeed, it is a singularly powerful one. A Racket program can extend a class at run-time, pass a class to a function, return a class from a method, store a class in a data structure, retrieve it from there. In short, Racket programs may manipulate classes as if they were ordinary valuesThis essay uses the word “value” in the sense of programming language semantics. Numbers, strings, structures, objects, and functions are examples. In particular, the use of “value” is unrelated to the (im)mutability of the entity. and compose and modify their class hierarchies at run-time.

The key innovation is Racket’s class expression, which evaluates to a class value. For example,
(class object%
   (init-field x)
   (super-new)
 
   (define/public (show)
     x))
evaluates to a class value that comes with one field specification and one method definition. Like all other values, Racket provides a number of basic operations that programs can use to manipulate class values: instantiation, extension, reflection. But because class values have the same status as numbers, structures, and closures, programmers can also define methods that operate on classes.

This essay sketches how Racket programmers use classes with an emphasis on first-class classes. For illustrative purposes, it shows how to implement big-bang, a 2D graphics framework for teaching novice students design principles. The last section sketches how DrRacket itself uses mixins in its startup sequence.

The essay expects that the reader has programmed with classes in some language and ideally to have used classes to build a GUI programs. It does not assume familiarity with functional programming, functional GUI programming or big-bang; indeed it explains only what is absolutely necessary about this background to motivate the example, starting in the next section.

This next section and the third one introduce plain class programming in Racket. Classes are Values explains how Racket classes are more powerful than Java classes. Classes in Action shows how to elegantly solve a serious performance problem using first-class classes. The last section sketches another application of first-class classes in the Racket code base.

Acknowledgments We thank Stephen Chang and Matthew Flatt for comments on an early draft. Matthew is also the primary designer and implementer of Racket’s class system.

1 Functional GUIs

In the context of a course on programming with algebra,How to Design Programs [2nd ed] a teacher may ask a programming novice to solve the following problem:

Your game needs a circle that shrinks every time the player manipulates the mouse or presses a key. Design a program that illustrates this behavior.

Using the 2htdp/universe and 2htdp/image libraries, the student may come up with the main function in the left column:
(require 2htdp/image)
(require 2htdp/universe)
 
; type State is Nat
 
(define (main)
  (big-bang
     ; initial state:
     10
     [to-draw render
              220
              220]
     [on-mouse less1]
     [on-key less1]))
(require 2htdp/image)
 
 
; type State is Nat
 
(define (main)
  (send (new world%
           [state0 10]
           [to-draw render]
           [width 220]
           [height 220]
           [on-mouse less1]
           [on-key less1])
        start))
Roughly speaking, big-bang is a state-machine. The programmer specifies an initial state, a bunch of event handlers, and a function that renders the current state as an image. Each event handler accepts big-bang’s current state plus event-specific information; it returns a new state, which big-bang squirrels away. Here the initial state is 10, and as figure 4 shows, both the mouse and the key event handler subtract 1 from the current state, regardless of what event they process. The to-draw clause specifies render as the function that expresses big-bang’s current state as an image and that this images is supposed to be displayed on a 220 x 220 pixels canvas.

The 2htdp/universe library compiles the student’s program to (roughly) the code in the right column. Since Racket’s GUI API relies on the class system, the main function creates an instance of a class called world%,By convention, a variable with a name ending in % stands for a class. handing along big-bang’s keyword arguments (after some parsing) as keyword arguments to new; when the object is created and initialized, main calls the object’s start method.

; Nat Any* -> Nat
; subtract 1 from the current state,
; regardless of the event
(define (less1 cd . other)
 (- cd 1))
 
; Nat -> Image
; render cd as a red circle
(define (render cd)
 (define r (if (>= cd 0) cd 0))
 (circle (+ 10 (* 10 r)) "solid" "red"))

Figure 4: Rendering and event-handling functions

The rest of this essay explains how the world% class functions. What the interested reader has to know about the big-bang mechanics is that while the initial state value and the to-draw clauses are mandatory, the on-mouse and on-key clause are optional.Another optional big-bang clause is on-tick, which runs a handler every time the clock ticks. If a student omits either clause, main does not handle mouse clicks or key presses. In contrast, the to-draw clause is mandatory and so is the specification of an initial state. The programmer may pick any set of values as the state space; students are expected to specify the state space in a comment, such as the one in the 2-column code sample above.

2 Classes in Racket

At first glance, a Racket class resembles its cousins in C#, Java, or most class-based object-oriented programming languages. A program can define a plain class, instantiate it, send messages to the resulting object, and extend the class. Furthermore, Racket’s GUI API employs a classic framework of classes, organized in a hierarchy, expecting a client program to derive class extensions to implement functionality.

2.1 Of Classes and Objects

Consider the definition of the world% class in figure 5. It says that the class consists of four pieces:
  • several fields that require initializations: state0, to-draw, width, height;

  • two fields that require optional initializations: on-mouse, and on-key;

  • one public method: start; and

  • some private fields: frame, visible, and editor-canvas.

Translating this definition into a conventional object-oriented language is straightforward, assuming you know the GUI API of the language because world% is all about managing a graphic user interface.

; the world as an object that reacts to key events and mouse clicks
(define world%
  (class object%
    (init-field
     ; exists type State
 
     ; State
     state0
     ; State -> Image
     to-draw
     ; Number
     width
     ; Number
     height
     ; [State Nat Nat MouseEvent -> State] or #f
     (on-mouse #f)
     ; [State KeyEvent -> State] or #f
     (on-key #f))
 
    ; -> Void
    (define/public (start)
      (send editor-canvas min-client-width (+ width INSET INSET))
      (send editor-canvas min-client-height (+ height INSET INSET))
      (send editor-canvas focus)
      (send frame show #t))
 
    ; PRIVATE: the content of figure 6 goes here
    ...
    (super-new)))

Figure 5: A plain class in Racket (public part)

...
; (instance-of Frame%)
(define frame
  (new frame%
       (label "The World Canvas")
       (alignment '(center center))
       (style '(no-resize-border))))
 
; (instance-of World-pasteboard%)
(define visible
  (new world-pasteboard%
       [state0 state0]
       [to-draw to-draw]))
 
; (instance-of World-editor-canvas%)
(define editor-canvas
  (new world-editor-canvas%
       [on-key
        (lambda (ke)
          (define state (send visible get))
          (send visible update! (on-key state ke)))]
       [on-mouse
        (lambda (x y me)
          (define state (send visible get))
          (send visible update! (on-mouse state x y me)))]
       [good-mouse?
        (lambda (x y me)
          (or (and (<= 0 x width) (<= 0 y height))
              (member me '("leave" "enter"))))]
       (parent frame)
       (editor visible)
       (stretchable-width #f)
       (stretchable-height #f)
       (style '(no-hscroll no-vscroll))
       (horizontal-inset INSET)
       (vertical-inset INSET)))
...

Figure 6: A plain class in Racket (private part)

A second look reveals a seemingly minor difference between Racket and other object-oriented languages. While most such language mingle the act of naming a class and defining it, Racket separates the two. Here Racket’s define associates the identifier world% with the value of an expression, which happens to be to a class value. In other contexts, define may associate an identifier with a number, a function, or an object.

The class expression itself starts with a reference to object%, the root of the class hierarchy. A Java programmer may instead write extends Object (or may rely on the default specification). In Racket, this position is actually evaluated and must yield a class value.The immutability of the class value provides additional protection especially when compared to class values in Python, which are really just has tables. The rest of the class expression calls for class features: public or private field definitions, public and private methods definitions, and even arbitrary expressions. As in Java, class features have a fixed shape, and a class is an unchangeable entity for the rest of the program.

When Racket instantiates such a class, it sets up the fields and method definitions, collects the expressions, and evaluates the latter in sequence—as if they had all been written in a constructor or an initialization method. The class system requires that a (super-new) expression shows up somewhere in this sequence;In general, super-new consumes required initial values, but in this case there aren’t any. its evaluation sets up the superclass features that belong to the newly minted object.

Here is a careful deconstruction of this class instantiation used in Functional GUIs:
(define a-world
  (new world%
       [state0 10]
       [to-draw render]
       [width 220]
       [height 220]
       [on-mouse less1]
       [on-key less1]))
It uses define to associate the identifier a-world with a new instance of the world% class. The resulting object has its six public fields initialized to 10, the render function (from figure 4), 220, 220, and twice the less1 function, respectively.

The interface of a-world supports one method call: start, though a program may also use get-field and set-field! to retrieve and modify the values of fields. Here are some sample interactions with this object:
> (send a-world start)

... see screenshot ...

> (get-field width a-world)

220

> (set-field! width a-world 440)
> (get-field width a-world)

440

Figure 7: Screen shot

Like many introductions to programming with classes, world% also relies on the world of graphical user interfaces to create an interesting example. As figure 6 shows, the private part of this class sets up three private fields:
  1. frame, which is an instance of the top-level window class with required settings for its initial public fields;

  2. visible, a pasteboard object, which is an editor that allows the explicit positioning of items, including images;

  3. editor-canvas, which establishes the context for editors such as a pasteboard.

The latter two aren’t instances of Racket’s plain pasteboard% and editor-canvas% classes, respectively, but world-specific extensions of these classes.

2.2 Class Extensions

Racket’s GUI framework is like that of many object-oriented languages. It provides a number of useful base classes with which programmers can create simple GUI programs, but for even moderately interesting interfaces, a programmer must derive a new class from one (or several) of these base classes in order to implement the desired behavior. In the simplest case, the derived class adds behavior to the base class via new public methods.

Figure 8 shows how to implement the world-specific pasteboard editor in this manner. The world-pasteboard% class extends the Racket pasteboard implementation. Hence, the class expression uses pasteboard% instead of object% in the super-class position.

; a pasteboard editor managing the state of the world & its view
(define world-pasteboard%
  (class pasteboard%
    (init-field to-draw state0) ; as above
 
    ; State -> Void
    ; update the current state to s and
    ; display the state in visible using to-draw
    ; effect: mutate state, modify view in pasteboard
    (define/public (update! s)
      (set! state s)
      (show (to-draw s)))
 
    ; -> State
    ; retrieve current-state
    (define/public (get)
      state)
 
    ; PRIVATE: the content of figure 9 goes here
    ...
    (super-new)
    ; more initialization:
    (reset!)))

Figure 8: A class extension in Racket

The derived class specifies two initial fields: to-draw and state0. Since pasteboard% does not come with mandatory initial fields, instantiating this world-specific pasteboard class requires just two values as the definition of visible in figure 6 already shows.

Besides the two new initial fields, the derived class adds two public methods to those inherited from its superclass: update! and get. While a statically typed language checks at compile time that these new public methods do not interfere with existing public methods, Racket must enforce this invariant when it evaluates the class expression. Once these checks pass, Racket creates an appropriate class value.

Similarly, Racket checks at run-time that every method call has a corresponding method definition in the targeted object, including for calls to inherited method. Thus, the world-pasteboard% class would use

(send this delete arg ...)

to delete something from the editor via an inherited method. If the superclass were to come without a delete method, Racket would signal a run-time error as it evaluates the send expression.

To accelerate the discovery of “method not found” errors for inherited methods, Racket allows programmers to specify such expectations via inherit clauses. Figure 9, which shows the private part of world-pasteboard%, starts with just such a specification. It says that world-pasteboard% expects six named methods from its superclass, and Racket checks this expectation as it evaluates the class expression. An inherit clause also simplifies the notation for invoking inherited methods; instead of using a send expression, the other class features may call these methods as if they were locally defined:

(delete arg ...)

or the call to lock in figure 9.

(inherit delete find-first-snip insert lock
         begin-edit-sequence end-edit-sequence)
 
; State
; current state of the world
(define state state0)
 
; -> Void
; initialiaze state and show its image in visible
; effect: mutate state, modify pasteboard
(define/private (reset!)
  (set! state state0)
  (show (to-draw state)))
 
; Image -> Void
; show the image in the visible world canvas
; effect: modify pasteboard
(define/private (show pict)
  (begin-edit-sequence)
  (lock #f)
  (define s (find-first-snip))
  (when s (delete s))
  (insert (send pict copy) 0 0)
  (lock #t)
  (end-edit-sequence))

Figure 9: A class extension in Racket (private part)

The private part of world-pasteboard% implements reset! and show, the two methods called from the public part of the class. As figure 9 shows, the show method is the workhorse, manipulating the editor with a delicate sequence of actions. While the details are irrelevant for this essay, the interested reader may wish to explore the meaning of these methods in the documentation.

; an editor-canvas editor that deals with key events and mouse clicks
(define world-editor-canvas%
  (class editor-canvas%
    (init-field
     ; type State
     ; KeyEvent -> State
     on-key
     ; Nat Nat MouseEvent -> State
     on-mouse
     ; Nat Nat MouseEvent -> Boolean
     good-mouse?)
 
    ; (instance-of Key-event%) -> State
    ; compute new state in reaction to key event
    (define/override (on-char e)
      (on-key (key-event->parts e)))
 
    ; (instance-of Mouse-event%) -> State
    ; compute new state in reaction to mouse event
    (define/override (on-event e)
      (define-values (x y me) (mouse-event->parts e))
      (when (good-mouse? x y me)
        (on-mouse x y me)))
 
    ; PRIVATE:
    ...
    (super-new)))

Figure 10: A second class extension in Racket

Figure 10 introduces one more element of Racket’s class language: method overriding. Like C# and unlike Java, Racket demands an explicit override specification for methods. Using this specification, it can check during the evaluation of a class expression whether the given superclass comes with the required public super method; if not, Racket can signal an error during class creation. While this check helps programmers find subtle mistakes early, it again allows Racket to specialize invocations of overridden super methods.

Concretely, figure 10 presents the world-specific canvas class. It extends Racket’s editor-canvas% class with two overriding methods: on-char and on-event. The first deals with key events, invoking the programmer-supplied on-key function on the relevant pieces of the data representation of a char event. The second processes mouse events, again with a programmer-supplied on-mouse function that receives only the relevant parts of a mouse event.

Use the complete code these world classes for to experiment with class-based object-oriented programming in Racket.

3 Classes are Values

As indicated in the preceding section,

(class ...)

does not define a class in the sense of C++, Java or C#; it creates a class value. Technically, class is keyword that marks an expression just like begin, let, or send are keywords that mark other Racket expressions. When Racket encounters an expression, it determines its value, and that value flows into the context. Since the preceding sections use class in conjunction with define,

(define c% (class ...))

it is easy to imagine that class definitions in Racket have a cumbersome syntax but are otherwise like those in conventional class-based object-oriented languages.

Introducing classes in this way helps carry over intuition from traditional languages to Racket. A novice can initially ignore the long-winded define & class syntax and move on. In order to appreciate the full power of Racket’s class system, however, a programmer must eventually understand classes as values and the operations that manipulate classes. The first subsection extends ordinary operations on classes to run-time class values, the second one sketches a simple example of exploiting their power via so-called mixins, and the last one introduces one example of a reflective operation.

3.1 Operations on Classes

Phrased in terms of operations on values, every programmer thinks of class instantiation and class extension, two widely used operations on classes. From a purely syntactic perspective, the key difference between Racket and conventional languages is that neither of these operations expects a class name but a class value to instantiate classes or create new ones.

Given this explanation, it is easy to see how

(new (class object% (super-new) (define/public (hello) "world")))

works. The first position in a (new ...) expression must specify a class but not necessarily by name. A class expression is one acceptable substitute. Evaluate the above expression at the read-eval-print loop and watch it produce an object:
> (new (class object% (super-new) (define/public (hello) "world")))

(object:eval:2:0 ...)

Indeed, any expression that evaluates to a class value works in this position:
> (define (monday?)
    (= 1 (date-week-day (seconds->date (current-seconds)))))
> (new
    (if (monday?)
        (class object% (super-new) (define/public (hello) "world"))
        (class object% (super-new) (define/public (hello) "bye"))))

(object:eval:4:0 ...)

Here new instantiates one of two different class values, depending on what day of the week it is. Since both classes specify the same public interface—a hello method—the rest of the program cannot fail with a “message not understood” error. An instantiation such as the above can also deal with values for init-fields and immediate message sends:
> (new
    (class object%
          (init-field a)
          (super-new)
          (define/public (hello) a))
    [a 22])

(object:eval:2:0 ...)

> (send
    (new
      (class object%
        (init-field a)
        (super-new)
        (define/public (hello) a))
      [a 22])
    hello)

22

In a similar vein, the first position in a class expression does not expect the name of a class but a class value:
(define c%
  (class
    (class object% (super-new) (define/public (hello) "world"))
    (super-new)
    (define/override (hello)
      (printf "~a hello\n" (super hello)))))
This definition associates c% with a class that extends an in-lined class. Here is a step-by-step explanation:
  • First, (class object% (super-new) (define/public (hello) "world")) is a class derived from the built-in object% root class. It comes with one public method, hello.

  • Second,
    (class [...]
      (super-new)
      (define/override (hello) (printf "~a hello\n" (super hello))))
    extends [...], which must be a class value. From the rest of the expression, it is also clear that [...] must be a class that defines a public hello method. The result is a class that has the same interface as [...] with extended functionality for the hello method.

  • Finally, placing the first expression into the [...] position of the second means that c% is a class with a hello method that prints ‘world hello’ on a single line.

An experiment in Racket’s read-eval-print loop confirms this explanation:
> (send (new c%) hello)

world hello

Naturally a programmer can also use a conditional to specify a superclass:
(define a%
  (class object%
     (super-new)
     (define/public (hello)
        "a happy")))
 
(define b%
  (class object%
     (super-new)
     (define/public (hello)
        "a smiley")))
 
(define c%
  (class (if x a% b%)
    (super-new)
    (define/override (hello)
      (printf "~a hello\n" (super hello)))))
Here c% inherits from either a% or b%, depending on the current value of x. Try to infer the value of x from the following interaction:
> a%

#<class:a%>

> b%

#<class:b%>

> c%

#<class:c%>

> (send (new c%) hello)

a smiley hello

Since the word "smiley" shows up in the final output, the super call to hello in c% must have reached b%, which means that x must have been #f when Racket determined the value of the superclass expression.

In short, our experiments confirm that Racket supports class extension as a run-time operation on class values. The Racket code base exploits this power in many places and in many different ways. On one hand, this perspective enables programmers to separate class hierarchies into small, easy-to-explain pieces that are later composed into the desired whole. On the other hand, with dynamic class composition programs can splice functionality into a class hierarchy while staying entirely modular. That is, the building blocks are in separate classes, not in a super-duper superclass that unites unrelated parts of the class hierarchy.

3.2 Mixins, a First Taste
(define artist%
  (class object%
    (super-new)
    (init-field canvas)
 
    (define/public (draw)
      (set! canvas "drawing")
      canvas)))
 
 
 
 
(define artist
  (new artist%
       [canvas "pad"]))
(define cowboy%
  (class object%
    (super-new)
    (init-field holster)
 
    (field (hand #f))
 
    (define/public (draw)
      (set! hand holster)
      (set! holster empty)
      hand)))
 
(define cowboy
  (new cowboy%
       [holster "gun"]))
> (send artist draw)

"drawing"

> (send cowboy draw)

"gun"

Figure 11: Artists and cowboys

Figure 11 introduces the basis of a toy-size example to illustrate how programmers create programs that create a part of the class hierarchy at run-time. Take a look at the two, obviously unrelated class definitions in the figure. One introduces the artist% class, the other one a cowboy% class. Both classes define a draw method, but when these methods are run, they behave in different ways and return different results.

A Java programmer who wanted to add the same functionality to both is faced with the choice of duplicating code in subclasses or creating a common superclass. While the “duplicate code” alternative is universally considered as unethical, the “common superclass” alternative comes with its own problems. For one, a programmer may not have the rights to modify the two class definitions, in which case the “common superclass” alternative is infeasible. Even if the programmer can modify the two classes, creating a common superclass for artists and cowboys unifies two unrelated classes in a way that most software designers consider objectionable.

The introduction of first-class classes solves this conundrum in an elegant way. A programmer defines a function that consumes classes and derives subclasses with the appropriate behavioral extension or modification. Here is such a function for this toy example:
(define (ready-mixin draw%)
  (class draw%
     (super-new)
     (define/override (draw)
       (string-append "ready! " (super draw)))))
The function’s purpose is to map a class to a subclass. More precisely, ready-mixin consumes a class and returns a subclass, and this subclass overrides the draw method of the given class.

Functions such as ready-mixin are dubbed mixins. A program can invoke ready-mixin on a class, and the result is a subclass with a modified draw method:
(define artist-ready%
  (ready-mixin artist%))
 
(define artist-ready
  (new artist-ready%
       [canvas "pad"]))
 
(send artist-ready draw)
(define cowboy-ready%
  (ready-mixin cowboy%))
 
(define cowboy-ready
  (new cowboy-ready%
       [holster "pistol"]))
 
(send cowboy-ready draw)
> (send artist-ready draw)

"ready! drawing"

> (send cowboy-ready draw)

"ready! pistol"

Both artist-ready and cowboy-ready’s draw method add the word "ready!" to the result of their respective parent’s draw method, which remain distinct.

Another way of looking at this idea is that mixins provide a substitute for multiple inheritance.

Now mixing up artists and cowboys is silly. In the context of our running example, however, using mixins looks solves a serious problem elegantly. The next section explains how to use mixins in that world.

3.3 Reflective Operations

Beyond the usual operations on classes, Racket also provides operations for inspecting a class at run-time in a reflective manner. Suppose you wish to write a program that inject a method m into two different parts of the class hierarchy, regardless of whether the two branches come with such a method already or not. According to our explanation of define/override above, solving this problem appears impossible at first glance.

Let’s start with a concrete example of a two-pronged hierarchy:
(define with-m%
  (class object%
    (super-new)
    (define/public (m) 0)))
 
(define without-m%
  (class object%
    (super-new)))
As the names say, the with-m% class comes with a method m while without-m% does not. One way to state our problem is to say that we wish to define the add-m-mixin function and that this function maps one class to another with a specific m method.

In principle, this add-m-mixin must have roughly the following shape:
(define (add-m-mixin super%)
  (if [...]
      (class super%
        (super-new)
        (define/override (m) 1))
      (class super%
        (super-new)
        (define/public (m) 1))))
Furthermore, the missing piece, indicated with [...], must check whether super% has an m method or not. We can formulate this check with a pair of reflective operations:

Let’s illustrate this kind of reflection with a sequence of interactions:
> (define x
    (class->interface with-m%))
> x

#<interface:with-m%>

> (method-in-interface? 'm x)

#t

> (define y
    (class->interface without-m%))
> y

#<interface:without-m%>

> (method-in-interface? 'm y)

#f

Based on these examples, it is clear that add-m-mixin needs the following expression
in place of [...].

Once the mixin function is complete, we can easily confirm that it adds the desired m method regardless of its superclass:
> (= (send (new (add-m-mixin with-m%)) m)
     (send (new (add-m-mixin without-m%)) m))

#t

4 Classes in Action

The world-editor-canvas% class of figure 10 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 12 displays the two mixin methods, both of which belong to the (private part of the) world% class defined in figure 5.

; 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 12: 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 13.

; (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 13: 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.

5 DrRacket: Start Me Up

When DrRacket starts up, it configures itself from information it finds in so-called “info” files in the various library collections. If such an info file specifies that the library wishes to plug in a component, DrRacket loads the component. The loading process can register mixins with DrRacket, which are added to the IDE’s top-level frame at the appropriate moment.

"drracket-startup.rkt"

#lang racket
 
(require "drracket-plugin.rkt")
 
; collect all plugins from the info.rkt files in tool directories
(define plugins (list "drracket-info.rkt"))
 
; dynamically load the plugins so that they can register themselves
(for ([plugin (in-list plugins)])
  (dynamic-require plugin #f))
 
(define the-top-level-frame%
  (send mixin-registry get-drr-frame%))
 
(define the-top-level-frame
  (new the-top-level-frame% [label "DrRacket"]))
 
(send the-top-level-frame show #t)

Figure 14: The start-up code for DrRacket

Figure 14 is a sketch of DrRacket’s start-up module. The module first imports functionality for registering the mixins. The second line specifies a list of component files for this sample code; in reality, this step searches directories for info files and retrieves the desired information from those. Once the plug-in information is available, the designated components are dynamically loaded. This dynamic-require step allows components to initialize themselves and to register mixins with DrRacket as needed. Since a dynamically required module shares state with an already loaded module, the registry works in principle in a straightforward manner:
  • a component uses a register method to add information, and

  • DrRacket uses a retrieval method, dubbed get-drr-frame%, to obtain the information.

Figure 15 displays the full definition of the registry class and its single instance.

With first-class classes, a singleton pattern is just a class expression nested in new.

"drracket-plugin.rkt"

#lang racket/gui
 
(provide
 ; object with two public methods:
 ; register and get-drr-frame%
 mixin-registry)
 
(define mixin-registry
  (new
   (class object%
     (super-new)
 
     ; Window -> Window
     (define (mixin x) x)
 
     ; [Maybe Window]
     (define drr-frame% #f)
 
     ; (Window -> Window) -> Void
     ; register another mixin m with drracket
     ; effect: accumulate m with mixin via function composition
     (define/public (register m)
       (cond
         [drr-frame% (error 'register-mixin "too late")]
         [else (set! mixin (compose mixin m))]))
 
     ; -> Window
     ; create the top-level frame from current state of mixin
     ; effect: set drr-frame% to its final state, a top-level frame
     (define/public (get-drr-frame%)
       (unless drr-frame%
         (set! drr-frame% (mixin frame%)))
       drr-frame%))))

Figure 15: DrRacket's plug-in registry

The information that DrRacket actually retrieves is the class from which it then creates its top-level window. See the last few lines of figure 14. In doing so, DrRacket relies on the protocol that mixin-registry in figure 15 implements. As you can see, the register method consumes a mixin function and composes it with the function created so far, stored in the private mixin field. Since the field’s initial value is the identity function, DrRacket uses the plain frame% when it doesn’t find any plug-ins.

When DrRacket uses get-drr-frame%, the method sets a flag. The register method checks this flag before accepting any mixins. By setting the flag, DrRacket disallows the addition of components after the top-level frame is popped up.

"drracket-info.rkt"

#lang racket
 
(require "drracket-plugin.rkt")
 
; Window -> Window
; a mixin that adds a message to the top-level frame
(define (sample-mixin base%)
  (class base%
    (super-new)
    (new message% [parent this] [label "mixin added"])))
 
(send mixin-registry register sample-mixin)

Figure 16: A DrRacket plug-in component

Figure 16 finally displays a simple component file. The module requires the plug-in registry and uses its register method to add the sample-mixin functionality. The latter is a procedure that accepts a top-level window and returns a class that extends it. This derived class simply pops up a message window displaying the string "mixin added".

Of course, realistic plug-ins perform many other computations. For the actual protocol, see the DrRacket Plugins documentation and how it relies on dynamic composition of mixins. Use the complete code for from the above figures to see for yourself how DrRacket’s protocol functions.

Additional Information A publication in the proceedings of the Asian Programming Languages, Applications, and Systems conference has additional information about Racket classes, especially how to mimic existing idioms with them.