On this page:
2.1 Of Classes and Objects
2.2 Class Extensions

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 2. 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 3 goes here
    ...
    (super-new)))

Figure 2: 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 3: 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 hash 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 1), 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 4: 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 3 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 5 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 6 goes here
    ...
    (super-new)
    ; more initialization:
    (reset!)))

Figure 5: 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 3 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 6, 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 6.

(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 6: 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 6 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 7: A second class extension in Racket

Figure 7 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 7 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.