[PREV]  [PREV]  [PREV]

SCHEME EXTENSIONS


We have extended Scheme in two main directions: Turtle graphics and multithreading:

The turtle object lives on a board on which it can move and draw. A turtle can also communicate with the board and "see" colors or other turtles. Galapagos Scheme provides a set of primitives to create and manage such turtles and the boards they live on.

A set of additional primitives enable the programmer to create multiple interpreters that run concurrently. The environment model was extended to support shared or thread-specific environments, and primitives to handle multiple environments were also added.

THE NEW ENVIRONMENT MODEL

A frame is a table which maps (or binds) names to values. An environment is a chained list of frames in which the interpreter works.

For example the command:

(define x 7)

creates a binding between x and the value 7.

A typical environment looks like this:





Each rectangle denotes a frame and inside it are the bindings it holds. The rightmost rectangle is the global environment.

When a variable is evaluated, the interpreter first tries to find it in the current frame. If a suitable binding can't be found, the interpreter moves to the next frame in the environment and searched for a binding there.

In our example if we write x in the command line we'll get 12 as the answer because all such computations take place at the global environment.

A function application is always evaluated with respect to the environment the in which it was defined. (A function "holds" its environment, hence the name "closure") This means that if we write

(define (f) x)

and then

(f)

the result will be

12.






In traditional SCHEME we have only one interpreter running at a time, so at a given point of calculation there is only one environment. In Galapagos multiple interpreters can run at the same time, and each of them has its own environment.

Under Galapagos's environment model, every interpreter works on its branch of an "environments tree", but can also evaluate an expression in any other environment in the tree. If two interpreters have a shared node on the tree, then from that point up (towards the global environment) all the bindings are the same for the two interpreters. The global environment is shared by all interpreters. If an interpreter changes the value of some variable, the binding is changed in all other interpreters that have access to the frame where the variable was declared. Obviously, caution should be taken when writing to a shared variable.

As an example, if a user created two new threads, called A and B, and then evaluated:

First thread> (define x 7)

(define (f) ...)

Thread A> (define (g) x)

Thread B> (define x 4)

this is how the environment model would look like:


Now consider what happens when the user evaluates this:

Thread A> (set! f g)



Now, evaluating (f) in thread B will call the function defined in thread A. It will return 12 which is the value of x in f's environment. If thread A defines a new x (using define), it will affect the result of subsequent calls to f.

Sharing environments and frames are Galapagos's way of allowing shared data. By default, however, most calculations are done at thread-specific frames. (More on this subject in the next section). Primitives are supplied to explicitly reference and manage environments: clone-environment can create a copy of an environment (so changing a binding in one copy does not alter the other); extend-environment and pop-environment can add and remove frames to and from environments. More environment functions are found in the next section of this document.

THREADS

A thread is a SCHEME interpreter. The base environment of a thread is the thread's topmost environment. All forms entered at the thread's console or as arguments to new-thread are evaluated in the base environment. In traditional SCHEME there is only one thread of execution. It is a single thread and its base environment is the global environment. In Galapagos many interpreters can run concurrently, and each have its own environment (changing dynamically as computation progresses) and a base environment (which can be explicitly changed).

One thread is created by Galapagos upon startup. It is called the main thread, and it lasts for the whole Galapagos session. Its base environment is the global one - much like a traditional Scheme interpreter. Additional threads can be created using new-thread. These have their base environment set to a new environment, containing a single fresh frame pointing to the global environment. Any calculation done at the new thread's console takes place in this thread-local environment. However, if closure functions are used, then their evaluation may take place at the global environment or at some other thread's space, depending on where that function was defined.

When a thread finishes the execution of its commands it terminates. There are two exceptions to this rule:

  1. The main thread can be only terminated by using quit-program.
  2. A thread that is bound to the console will not terminate at the end of execution but will wait for the next command input.

Threads can communicate using the predicate tell-thread or by using the asynchronous message-queues system described later. In addition, SCM's arbiters were improved to be multithread safe. An arbiter object can serve as a guard on a critical section (where only one thread can work at a given time) of the program. The relevant primitves are make-arbiter, try-arbiter, and release-arbiter.

Note: Galapagos supports Scheme's notation of continuations. However, two restrictions apply:

  1. Only "cheap" continuations are supported. That is, a continuation can only be called when it is still active. This allows for continuations to be used to implement exception handling, etc.
  2. Continuations can not cross thread boundaries. Also continuations, like any other Scheme object, can be handled by any thread, they can only be called from within the thread that created them. Trying to call a continuation from a different thread will cause an error.

CONSOLES

A console is a text window where the user can type commands (when the interpreter is idle) and see output. A console has a name which can be changed dynamically, which appears on its title bar. A thread may or may not be bound to a console. Information printed by a non-bound thread is lost; a non-bound thread waiting for input is stuck (unless provisions were made to allow a new console to be created by means of inter-thread communications.) If a non-bound thread has nothing to do, instead of waiting for input like a bound thread, it will terminate.

The main thread is always bound to a console. To bind a thread to a console use the command bind-to-console from within the thread.

DRAWING BOARDS

A board (window) is the graphical board on which the turtles and the user draw. It is a bitmap on which the user can draw interactively, by using the supplied GUI, or by creating turtles and instructing them to do the drawings.

The controls of the graphical user interface are located on the toolbar. The "shapes" of drawing are either a rectangle, a line or free hand drawing. The user pen can have three widths which are found under the User Pen menu. The numbers next to the sizes are the widths in pixels.

The colors of the user pen can be changes either from the User Pen menu or from the toolbar's color buttons. When choosing from a toolbar button, the user can know exactly what color (in RGB format) is used.

Each window has a name which is written on its title bar which can be changed using the rename-window! command. The board can be cleaned from all drawings using the clear-window command.

Since the board is a bitmap it can be saved to a BMP file using the save-bitmap command. A new background from a bitmap file can be loaded using the load-bitmap command.

TURTLES

A turtle is an object which is connected to a certain board. Each turtle has a pen with which it can draw on that board. The user can change the state of the turtle's pen including giving it the color of the board's background color (using the pen-erase! command). The pen will draw when it is "down" and will not draw when it is "up". Turtles can move between boards using the move-turtle-to-window command.

Each turtle holds its location as the a pair of coordinates where 0,0 is the upper left corner of the board and 800,600 is the bottom-right corner of the board. The turtle's location can be changed by giving the turtle commands such as forward!, backwards!, move-to! or by using the move button from the toolbar.

A turtle also has a heading which is the direction it will move on the forward! command. The heading is in degrees, 0 meaning upwards and increasing clockwise, thus 90 means rightwards and so on. A turtle's heading can be changed by commands such as right!, left!, and set-heading! or interactively, by using the move button on the toolbar and pressing the control key.

A turtle can be visible on the board or be hidden. When it is visible the user can decide if it wants a "solid" or "hollow" turtle using the make-hollow! and make-solid! commands.

A turtle is always connected to a certain board, which is the board it is drawing on. To create a new turtle on a board use the make-turtle or the clone-turtle commands. A new turtle (created with the make-turtle command) will be black, solid, heading north and with it's pen down, in the center of the board. A cloned turtle will have exactly the same inner state as its parent.

A turtle can also look at the board and see other turtles or colors. By using the command look the user can tell the turtle the area in which to look and what to look in this area.

Example:

The command

(look t 50 20 '(0 0 0))

will cause the turtle t to look for black pixels in the area in radius 50 from the turtle and 20 degrees to each side of the heading line, shown here in blue:


INTERRUPTS (NOTIFICATIONS) AND MESSAGE HANDLERS

A turtle can have interrupts. Each interrupt is defined in terms of turtle's vision. Whenever a turtle starts or stops seeing a given target, a predefined message is sent to the thread which asked the interrupt. The user can determine if the interrupt will notify on every change (like a "can see"- "can't see" toggle) or only when one change happens (only "can see" or "can't see" toggle). A message can be any valid SCHEME object.

Example:

The command

(turtle-notify t "I-see" "I-don't-see" 50 20 color-black)

tells the turtle t that every time it sees the color black it should send the message "I-see" and when it doesn't see the color black any more it should send the message "I-don't-see".

Below is an example of what messages (or no message) t will send while it is moving forward, encountering two black lines on its way:




If the interrupt was of kind "can see" (notify-when command) only the "I-see" messages were sent, and if it was a the "can't see" interrupt (notify-when-not command) only the "I-can't-see" style messages were sent.

A special "user interrupt" is invoked when the user right-clicks the turtle or a window. The notify-on-click command is used to enable this.

A thread can tell the turtle to delete a certain interrupt using the turtle-no-notify command. It can also tell the turtle to disable all interrupts using the stop-notifying command.

In order to process the messages sent from a turtle's interrupt (or from another thread, as presented later) the thread must install a message handler which is a one argument function. If no handler is installed the turtle's messages are lost. There can be only one handler per thread. If the handler is installed then every time an interrupt is invoked, the handler function in called with the message sent by the interrupt as its argument.

Example:

We declare the function:

(define (my-print x) (write x) (newline))

and then install my-print as the current handler with:

(install-handler my-print)

from now on every message a turtle's interrupt sends will be printed on screen. If my-print was installed in the previous example then we would have seen:

"I-see"

"I-don't-see"

"I-see"

on the screen.

A good example to view the interrupts in action is to make a turtle turn each time it sees a color and then let it wander aimlessly. Then use the user pen to draw lines the turtle's way and watch it change direction. See the PINGPONG.SCM file for a demo program.

The message-handler concept can be used as a mean of synchronous inter-thread communications. The tell-thread functions sends a message to a thread, which will be treated as an interrupt-generated message: It will cause the thread's message handler function to be called with tell-thread's argument as its own. This allows one thread to initiate a computation in a different thread's environment and CPU space synchronously and without sharing environments.

MESSAGE QUEUES

In order to let threads communicate between themselves asynchronously a system of message queues was developed. A queue is an object which stores and relieves messages, stored in FIFO (First In First Out) style, meaning messages are read in the order of arrival to the queue. Supported messages are pairs of type and body, both of which can be any valid SCHEME object. Looking for messages of a certain type is also supported, allowing implementation of more flexible communication schemes than plain FIFO.

A thread can check if there are any messages in a given queue, if it does get-message without checking first if there are messages in the queue it will wait a given time-out or forever if no time-out is given. If by the end of the time-out no message were in the queue the result will be false.

Example:

In this example a queue q is created then a message is posted into the queue and read from it.

(define q (make-queue))

(post-message q (cons 'type-circus 'bozo-the-clown))

(define msg (get-message q))

now the command

(car msg)

will give

type-circus

and the command

(cdr msg)

will yield

bozo-the-clown.


 [TOP]  [PREV]  [PREV]  [PREV]