Slate's Object Model

Everything that can be identified in Slate is an object: literal numbers or strings, files, sockets, data produced by programs, even code. All these objects are directly accessible through the REPL (Read-Eval-Print Loop, the interactive shell). You can look at existing objects, create your own objects, modify existing objects and immediately see the effects of your changes without having to go through a re-compilation step.

Expressions entered at the REPL are parsed and compiled to Slate byte-code, which is handled to the VM for execution. All objects are stored together in a file called the image. You can create as many images as you like, each with its own world of living objects. A typical use is to keep a backup image for emergency cases, and then one image for each project you are working on. Images are binary files, but saving Slate code in separate files will also be supported, for cases when the use of images would be impractical.

Although everything in Slate is an object, Slate does not use class hierarchies to organize the behavior of objects, as is the case for example in C++, Python or Smalltalk. Instead of relying on class hierarchies and inheritance, it uses graphs of objects with delegation relationships. As a first idea on how Slate finds methods on objects (and thus executes code), consider the simple case where a single object is requested to execute some method. If the method is found "on" the object, it is executed, otherwise, the request will be forwarded in turn to the objects listed as its delegates in the graph until either one can fulfill the request, or the request is rejected as no object can understand it. We will have more to say about how Slate selects methods on objects shortly.

Slots

Each slate object can be seen as two sets: the first set is called its set of slots, and the other its set of roles. Slots look like what is called attributes in other languages, with the difference that slots are defined by the object that holds them (instead of being defined by some other entity such as a class). Each slot has a name and a value. The name is any valid Slate symbol, and the value is a reference to another object. That is the conceptual model and does not necessarily mean that slots are implemented in this way.

Each slot of an object may be marked as a delegating slot when it is created, for purposes of finding methods. The method lookup algorithm will be explained in detail when we introduce roles.

When a slot is created, an accessor and a mutator method are automatically created to read and set its value. The default value of a slot is the object Nil (representing the absence of value), but this can be overridden at slot creation. Slots may also be created immutable (read-only).

A Note on Syntax

The syntax of Slate resembles closely that of Smalltalk. It is however different from that of Python or C++, but its differences make it more systematic and easier to parse and understand, as it relies on fewer keywords and constructs. This might sound like too much simplicity to you, and you might wonder if you will be able to express everything you need. The kind of simplicity introduced in Slate is enough to make its syntax and semantics consistent, so that reasoning about your programs is easier than in some other languages. Yet its authors took great care to make the language as expressive as possible, so that Slate is able to lend itself to the work at hand, without drowning the ideas into syntax issues.

Each Slate statement looks like a simple sentence in English: subject, verb, complements, always in that order. The subject is an object, the verb a method to select and the complements, if any, are the parameters to the method. You write then:

In SlateIn Python
dog barkdog.bark ()
dog fetch: stick.dog.fetch (stick)

The rules of thumb for the syntax are:

The last rule has to be refined for the case where you need to pass several arguments to one method. That case resembles Python's keyword arguments and is written as follows:

In SlateIn Python
archer aimAt: dragon with: arrow.archer.aim (target=dragon, weapon=arrow)

The signatures of methods (called selectors after Smalltalk's practice) fall into the following categories:

  1. unary methods, such as 1 negated or dog bark.

  2. binary methods, such as arithmetic methods: 1 + 2 selects the method #+ on the objects 1 and 2.

  3. keyword methods, such as #aimAt:with: in the previous example.

More information can be found in the Slate Programmer's Manual in the chapter for expressions.

When methods from several categories are mixed, the order of precedence is that of the preceding rules. Evaluation always proceeds from left to right.

ExpressionEvaluates to
2 + 3 negated.2 + (3 negated).
2 + 3 * 5.(2 + 3) * 5.
2 negated raisedTo: 3.(2 negated) raisedTo: 3.
2 raisedTo: 3 negated.2 raisedTo: (3 negated).
2 + 3 raisedTo: 5.(2 + 3) raisedTo: 5.
2 raisedTo: 3 + 5.2 raisedTo: (3 + 5).
2 raisedTo: 3 + 5 negated.2 raisedTo: (3 + (5 negated)).

Parentheses are used to alter the evaluation order, as is the case in many other languages. Thus, 2 + 3 * 5 is 25, but 2 + (3 * 5) is 17. The regularity of the parse was preferred over usual arithmetic conventions, as is the case in Smalltalk. While this may seem an odd choice at first, it also help avoid many parentheses in the text of programs, making it more free from syntactical clutter.

There are still a few more syntax rules, which we will introduce along this tutorial as we need them.

Creating Slots

There is an object in Slate that represents the place from which all other objects may be accessed. This object is called the lobby. The slots of the lobby are mainly namespaces, but they can hold any object. We will discuss the use of namespaces later; for now, you can just think of a namespace as any other object, since we do not need its other properties.

Let us then create a new object, by simply adding a slot to the lobby to store that object. In the following line, we use the fancy name bubibim.image as the image we created when installing Slate.

$ ./vm bubibim.image
True
Slate: Growing heap to 5447500 bytes.
Slate 2> addSlot: #myObject.
(Types . prototypes . VM . myObject .
        globals . Mixins . traits )
Slate 3> 

The pound sign before myObject makes it a Slate symbol, a unique literal name that evaluates to itself. Had we not used it, myObject would have been interpreted as the name of a reference to an existing object and led to an error (since there is no object yet by that name).

The following line shows the set of slots for the lobby after the command is executed. There is a slot for our new object. The new slot we created took the default value of Nil (the object representing the absence of value).

Slate 3> myObject.
Nil
Slate 4> 

We can assign a value to the slot and read it back like this:

Slate 4> myObject: 37.
37
Slate 5> myObject.
37
Slate 6> 

Anything you type at the REPL prompt is evaluated in a context (hence an object) that has the lobby as one of its delegates. Therefore, typing lobby at the beginning of each of the preceding lines to explicitly set the context is not necessary.

It is also possible to remove slots:

Slate 6> removeSlot: #myObject.
(Types . prototypes . VM . globals .
        Mixins . traits )
Slate 7> 

Let us now give a value to an object at creation time. We use the #addSlot:valued: selector to that effect. The value attached to a slot can be anything you like: number, string, another kind of object... There is no need to create a specific slot for a number if you need to store a number, for example. All slots are dynamically typed, as in Python, Scheme, or Smalltalk.

Slate 7> addSlot: #myObject valued: 43.
(Types . prototypes . VM . myObject .
        globals . Mixins . traits )
Slate 8> myObject.
43

Suppose you need to store a string in the slot called myObject. This can be readily done as follows, without any special manipulation on the slot. However, in many situations, you will want to have slots that stick to a certain kind of object and do not become a place for anything and everything. Slate supports type assertions to help achieve this effect.

Slate 9> myObject: 'The quick brown fox jumps over the lazy dog'.
'The quick brown fox jumps over the lazy dog'

The selector #addImmutableSlot:valued: will create a read-only slot:

Slate 10> addImmutableSlot: #x valued: 37.
(Types . prototypes . VM . myObject .
        globals . Mixins . x . traits )
Slate 11> x: 43.
The following condition was signaled:
The method #x: was not found for the following arguments:
{(Types . prototypes . VM . myObject .
                globals . Mixins . x . traits ).
        43}

The following restarts are available:
0)      Inspect a stack frame
1)      Abort evaluation of expression
2)      Quit Slate
Debug [0..2]: 

This is our first contact with the Slate debugger. At the time the slot #myObject was created, two methods, with selectors #myObject and #myObject: were created to read and modify its value, respectively. The #addImmutableSlot:valued: selector forbids the creation of the method used to modify the slot value, resulting in a read-only slot. Our attempt to modify the slot's valued caused an exception to be signalled. We can see the error message and the back-trace. Typing in 1 when prompted for a place to restart execution from sent us back to the REPL and cleared the exception.

Debug [0..2]: 1
Nil
Slate 12> 

Let us end this first Slate session by quitting the image.

Slate 12> quit.
$