UNB/ CS/ David Bremner/ teaching/ cs3613/ tutorials/ CS3613 Tutorial 9

Background

We have talked a bit about how to impliment objects using lambda, and we're going to talk more about about it the next few weeks. In this tutorial we're going to have a look at the Racket object system and in particular how it's used in the Racket gui library.

We will use the dialect racket/gui in our examples today, so start your file with

#lang racket/gui

Creating objects

Like many languages, objects are created using new. In Racket, this looks just like a function

#lang racket/gui
(define frame (new frame% [label "I've been Framed"]))

There's a lot going on in that one line, but not much to look at when we run it. In Java and Python it's easy to forget the original metaphor of sending messages to objects, but Racket in it is explicit with

(send <object> message <arg1> <arg2> ...)

Your turn

Steal the appropriate send form from the first example in the gui manual to get a window popping up.

Analysis

Now that we know that does something, let's look at it a bit from a language perspective. For each of the following, try to classify it as a value (like lambda), or syntax (like if).

Even syntax (sometimes called special forms) mostly defines expressions.

The event loop

It turns out we just wrote a concurrent program: multiple things are happening at the same time. We get a hint that this is the case because we are returned to the REPL with our window still showing.

In the lower REPL window of DrRacket (or Emacs), type

(send frame show #f)

Notice the window goes away. The concurrency here is managed internally by an event loop. In order to really understand the difference with sequentially executing a sequence of functions or method calls, let's handle some user generated events.

Add the following code to your example

(define msg (new message% [parent frame] [label ""]))
(new button% [parent frame]
             [label "Panic"]
             [callback
              (lambda (button event)
                (send msg set-label "Don't Panic"))])
              

Analysis

We've been using, but ignoring, something Racket calls initialization arguments e.g. label and parent. These are conceptually very similar to constructor arguments in object oriented languages. Unlike Java they use a named argument syntax rather than a positional one (Python also has optional named arguments).

The most important initialization argument here is callback. This gives a function to run when the button is pressed. It's clear now that the control flow is not totally under the control of our code: like most gui frameworks, the runtime waits for an event and then hands us back control temporarily.

Some more things to notice from a programming language perspective

Your turn

Drawing

Let's add a drawing area to our UI, so we can print more reassuring text

Here's some sample code to set up a canvas and print some text in it.

(define canvas (new canvas% [parent frame]))
(define (panic)
  (let ([dc (send canvas get-dc)])
    (send dc set-scale 5 5)
    (send dc set-text-foreground "blue")
    (send dc draw-text "Don't Panic!" 0 0)
    (send dc set-scale 1 1)))

Nothing happens until you call the function (panic) from the REPL.

Your turn

Subclassing

A common way to reuse a class is to subclass it. In Racket this is done by using the class form.

Let's start with the following modification, which should not break anything.

(define my-canvas%
  (class canvas%
    (super-new)))
(define canvas (new my-canvas% [parent frame]))

Things to observe:

In order to overide a method in a subclass, we use define/override like follows

(define my-canvas%
  (class canvas%
    (super-new)
    (define/override (on-event event)
      (when (send event button-down? 'any)
        (panic)))))

Your turn

Modify the definition of my class so that button clicks cause diameter 10 circles to be drawn at the appropriate place. You can either use this to send messages to the current object, or use the inherit to make the methods you need available as regular functions.