On this page:
3.1 Operations on Classes
3.2 Mixins, a First Taste
3.3 Reflective Operations

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 8: Artists and cowboys

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