Language Tutorial

From SlateWiki

Table of contents

Introduction

This is a tutorial for the version 0.3 of Slate.

Slate is both a general purpose, fully object-oriented programming language and an environment. The language is the result of thorough research that draws inspiration and ideas from many other existing languages such as Smalltalk, Self, Common Lisp (CLOS) or Dylan among others, and tries to integrate and extend them. It strikes a balance between a simple syntax, a prototype-based object model and the use of powerful abstractions to allow for great flexibility in the design and maintenance of programs.

Some leading Slate features include: a consistent prototype-based object model, multiple dispatch and mix-ins, subjective programming, reflection, and a powerful, well-factored collection library. As Slate is still a work in progress, you can expect a lot more from it in the future.

The Slate environment in Slate 0.3 is an interactive shell with a text-only interface. This interactive shell lets you inspect existing objects, debug Slate code, evaluate any Slate expression, create new objects, or extend existing ones.

Support for native shell commands (the equivalent of the Unix system () call), as well as a foreign function interface to connect to existing libraries written in another language are available, and the native interface is still evolving.

The Slate language features can be considered stable, and the use of the interactive shell along with an editor (there's an Emacs mode and a vi mode for Slate) provides a usable environment to develop in Slate. However, due to its youth, the Slate environment still lacks many of the tools that come with any Smalltalk or Lisp environment (internal source code management, browsers, ...) Any contributions are warmly welcomed, of course :-) .

Slate 0.3 has its own virtual machine and image. The virtual machine is written in a subset of Slate called Pidgin, then translated from Pidgin to C. The translation step is fully done in Slate, too. The C code of the virtual machine is then given to a C compiler to produce the executable. It is planned to get rid of the C part some time in the future and have Slate dynamically compile itself to machine code. Slate 0.3 is the first bootstrapped Slate version. Previous ones relied on an underlying Lisp compiler and interpreter for evaluation, image dumping and garbage collection.

For detailed, comprehensive information on Slate, check the available reference material.

We hope you'll have as much fun with Slate as its current developers and users.

Installation and Usage

Requirements

In order to install and run Slate 0.3, you will need a C compiler, a make utility, an archiver and compression utility (such as tar and gzip, or WinZIP). These are most likely available in your distribution if you are running Linux or one of the BSD flavors. There is no requirement to use GNU make: the makefile is quite simple and is likely to be properly handled by any make utility around. However, using GCC for your C compiler will put you on the safe side. Other compilers like VC++ can produce erroneous binaries when certain optimizations are turned on (this is fixed in 2005 beta releases). Windows users can install MinGW (http://www.mingw.org/) to make stand-alone native binaries or a more hefty Cygwin (http://www.cygwin.org/) environment to get started with Slate.

The Slate VM's use of the C library is minimal and standards-compliant, so compiling Slate on various other platforms should not be too difficult. The Supported Platforms notes contain some details on platforms that have been tested and what may require changes elsewhere.

In any case, please bear in mind that Slate is still alpha software. While the level of bugs is quite low, there still are missing features and rough edges at places.

Downloading

Here is a step-by-step list of actions to perform in order to get Slate up and running on your computer:

  1. Download the current snapshot of Slate available at http://slate.tunes.org/downloads/slate-current.tar.gz. Current snapshots are regularly made from the CVS source tree when the stability of the tree is acceptable and a number of fixes or new features is available. Bugs found in the releases (such as 0.3) are fixed in such snapshots. More patient or less adventurous people may wish to rely on released snapshots only and download the latest release snapshot available at http://slate.tunes.org/downloads/slate-0.3.5.tgz instead.
  2. Download the VM source files and bootstrap image for Slate. If you have the GNU program wget (also available for Windows users (http://unxutils.sourceforge.net/)), you may skip the rest of this section, since the Makefile will automatically invoke the program to retrieve the missing ingredients. These files are available at http://slate.tunes.org/downloads/alpha/. You will need the files called vm.c and vm.h, which are the C code of the VM generated from Slate, and one bootstrap image. Bootstrap images depend on the endianness of your computer. Users of little endian (resp. big endian) architectures should download the file named little.image (resp. big.image).

Unfold the Slate archive (slate-current.tar.gz in our example). On an Unix-like system, the command you need is:

$ tar xzf slate-current.tar.gz
$

This will create a directory called slate-current.

Go into this new directory and copy the 3 other files you downloaded.

Building

Now is the time to compile the VM. Execute make, or use the Visual Studio Project file provided for the Microsoft C toolchain. Some example output from make:

$ make
Welcome to Slate: Less talk, more rock!
 Version: 0.3.5
  System: Linux [little-endian]
   Build: optimized
Compiling ...
...
Linking ./vm
$

At this step, the virtual machine executable is built.

Creating a full Slate world

The next step is to run the bootstrap image, which executes code at startup to unfold all methods and objects in the system. You should expect this step to take a little time. In the example below we use little.image which is appropriate for little-endian systems such as X86. If you are on a big-endian system like PPC substitute big.image where you see little.image below. When starting Slate with a bootstrap image loading takes quite a bit longer than with a saved image. Please be patient. After saving an image as outlined in the next step starting Slate is much much faster.

$ ./vm little.image
Bootstrapping libraries... (this may take a while. Save the image when done).
Slate: Growing heap to 4711472 bytes.
Performing post-bootstrap actions...
Loading P'src/mobius/syntax/quote.slate'
....(much more like the above line)
Hi, there!
Please save your image now, (eg. "Image saveNamed: 'slate.image'.") to avoid repeating this initialization.
Slate 1> 

The final step is to save the generated image under the name of your choice.

Slate 1> Image saveNamed: 'bubibim.image'.
Slate 2> quit.
$

This will save the state of the Slate world, the image, and bring you back to the shell. Don't worry too much about the syntax yet, except that you should type this in verbatim - Slate will also provide a banner with this text for convenience. To start Slate, you just have to execute the VM with the name of the image just generated. This will restore the state of all objects in the system to what it was when the Image saveNamed: method was invoked.

$ ./vm bubibim.image
True
Slate: Growing heap to 5447500 bytes.
Slate 2> quit.
$

The subjective dispatch, including subjects and layers, is not yet included in Slate 0.3.

Refer to the file README (http://slate.tunes.org/repos/main/README) in the top-level directory of the Slate distribution for instructions if and when you need to build a new VM.

Digging In

Objects

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 interactive REPL (Read-Eval-Print Loop, the shell) application. 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.

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.

The Compiler

Expressions entered at the REPL are parsed and compiled to Slate bytecode, which is code code that the VM executes to make the Slate world alive. All objects in memory are saved together in a file called the image, so-called because it is a snapshot of the running memory heap, including the exact calls made at the time the image was made. 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 objects and code in separate files will also be supported, for cases when the use of images would be impractical.

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

Syntax

The syntax of Slate resembles closely that of Smalltalk. For those coming from a Java or C++ background, a quick reference is available to give a basic feel for how basic syntax elements translate between the Java/C++ family and Smalltalk family: see Java and Smalltalk syntax compared (http://www.chimu.com/publications/JavaSmalltalkSyntax.html).

It is 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, and you might wonder if it can express everything you need. The kind of simplicity introduced in Slate is sufficient 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, and maybe complements, always in that order. The subject is on the left, the verb a method to select, and the complements, if any, are the parameters to the method, like the direct and indirect objects of an action. This is much like typical object-oriented languages' syntax, however, Slate and Smalltalk use whitespace to separate object and verb instead of a period, and arguments follow whitespace or colons instead of using parentheses as a container, to give a more sentence-like feel to reading source code. Here is a comparison with a typical language notation:

Brand X In Slate
dog.bark () dog bark

The object and the method's name to call are set apart with whitespace instead of a period. If nothing more is needed, no empty parentheses need to be typed. Here, we have dog which we tell to bark by sending it the bark message. We say that this message has a unary syntax, since it involves just one argument, its subject.

Brand X In Slate
dog.fetch (stick) dog fetch: stick

If an argument is needed, the method name is different, using a colon to indicate that a further element is expected. This is part of Slate syntax and not just a convention - wherever there is a colon ending a method name, that means there is an argument in that position to supply when calling it. So in this example, we have a distinct method named fetch: which specifies what to fetch; if we had a fetch message, it would not take another argument, and presumably would be to tell the dog to fetch the last thing it saw or maybe some other kind of default, but it would be distinct from fetch:. We call this kind of syntax a keyword syntax.

Brand X In Slate
archer.aim (target=dragon, weapon=arrow) archer aimAt: dragon with: arrow

When more than two arguments are appropriate, a method name will have more than one keyword-and-colon instead of using more punctuation. In this case, we are looking at a message aimAt:with: which has a usual subject, but also two complements, each following a colon. This is also called a keyword syntax, but this uses multiple keywords joined into one name or phrase.

This illustrates some of the syntax's flexibility in constructing a phrase notation for dividing up behavior. Instead of merely providing a name and a value, the ability to write down a whole phrase with place-holders allows the reader to see immediately (without having to look deeply at source or hope for documentation) what roles the method expects its complement arguments to play, like the preposition At: asks for a target, and with: asks for an implement or means.

A convention shown here is Slate's use of the camel-case convention, the use of bumping up letters to capitalized form in the middle of phrases to show word boundaries. This is preferred for consistency but not required.

Brand X In Slate
3 + 4 3 + 4

In Slate, operators also count as messages, but do not need colons to specify arguments. However, they can only ever take two arguments, and so are called a binary syntax for messages. If a unary operation is desired, a single word should be used, a unary message, such as 5 factorial or (3 < 4) not

The signatures of methods (called selectors after Smalltalk's practice) therefore 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; each keyword is grouped unless there are parentheses to clarify.

Each of these kinds of method syntax is also a precedence level, in the order of the list; unary binds more tightly than binary and then keyword messages in that order. Precedence otherwise proceeds from left to right if staged messages have the same precedence. There is no other precedence level system in Slate.

This can be uncomfortable at first , but it has some benefits:

  • The parser can remain simple and understandable - there are few precedence interactions to track.
  • It ensures that methods with any name or types may be created and used easily (again, the creator doesn't have to find or invent a precedence for something new).
  • It means that the shape of the code may be understood without referring to anything but the expression at hand.
  • A data-flow order will often just flow from left to right.
Expression Slate sees
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 seen as (2 + 3) * 5 and evaluates to 25, but 2 + (3 * 5) evaluates to 17.

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

Working with Slots

To work with objects, we actually need to link them via slots from objects that we already have. So Slate gives you by default an object which represents your place in the Slate world: an object in Slate that represents the place from which all other objects may be accessed. This object is called the lobby, so named because this is the place where objects enter the world. The slots of the lobby are mainly namespaces, but they can hold any object. A namespace is just like any other object, and in fact we will see later how to change one's place to any old object, but namespaces are specialized a little for organizing other objects; for now these difference will not matter.

We start Slate again with our ready-to-go image. We use bubibim.image, the image we created when installing Slate.

$ ./vm bubibim.image
Slate: Growing heap to 6813448 bytes.
Nil
Slate 2> 

Let us then create a new object, by simply adding a slot to the lobby to store that object.

Slate 2> addSlot: #myObject.
lobby
Slate 3> 

We called addSlot: here, with what looks like one argument, but there are in fact, two; the extra argument is the implicit context, what we called a place above. Its position is "right before" the addSlot: call. The pound sign before myObject makes a Slate symbol, a unique literal name that exists just to identify things and is an object in its own right. Had we not used that notation, 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 lobby object, which just tells us its name. However, there is a slot for our new object - we can ask for the slot names to see:

Slate 3> lobby slotNames.
{#traitsWindow. #prototypes. #globals. #Mixins. #Types. #VM. #systems. #previousREPLMethod.
    #myObject}
Slate 4> 

This time we called the lobby by its name, but there's something notable about this: the request for the lobby is another message itself, also sent to the implicit context. So we're asking the lobby for an explicit reference to itself, which is a good way to get a specific place as an argument. Usually, the REPL's place will inherit from the lobby, so that this is a reliable way of getting back to the root of things.

Now that we know this, we can also query the slot itself. The new slot we created took the default value of Nil, the object representing the absence of value.

Slate 4> myObject.
Nil
Slate 5> 

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

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

It is also possible to remove slots:

Slate 7> removeSlot: #myObject.
lobby
Slate 8> 

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 8> addSlot: #myObject valued: 43.
lobby
Slate 9> 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'
Slate 10> 

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

Slate 10> addImmutableSlot: #x valued: 37.
lobby
Slate 11> x: 43.
The following condition was signaled:
The method #x: was not found for the following arguments:
{lobby. 43}

The following restarts are available:
0)      Abort evaluation of expression
1)      Quit Slate

Enter 'help.' for instructions.
Debug [0..1]:


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 : 0 (verbatim) when prompted for a place to restart execution from sent us back to the REPL and cleared the exception.

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

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

Slate 12> quit.
$ 

The Execution Model

Let us take a look at the execution model of Slate. In some languages such as Python or Smalltalk, the basic object behavior is as follows: you send a message to an object, and that object selects, using its class hierarchy, the corresponding method to execute. If none is found, then an exception is raised. Other objects you pass as arguments to the method do not play any role in the selection of the method to execute. If the method executes, you get another object in return as the result of the execution (or possibly an exception). That kind of method selection is called single dispatch, as a single object is used to look for the method to execute.

In contrast, Slate supports a multiple dispatch scheme. Instead of sending a message to a single object, you send it to a tuple of objects. Intuitively, the tuple consists of the object that would have been the receiver of the message in a single dispatch scheme, followed by all the objects that would have been the arguments to the method, in order. The objects in the tuple collectively select the method to execute, using rules that we will describe in a moment.

Roles

Roles are an important and unique feature of Slate. The definition of roles is not quite simple, although in practice, the use of roles together with multiple dispatch is quite intuitive.

Let us learn roles from an (everyday ;-) ) example. Suppose we have the following line of Slate code: archer aimAt: dragon with: arrow.

The symbols #archer, #dragon and #arrow stand for already existing objects. The signature of the method is #aimAt:with: (remember that symbols in Slate start with a pound sign). If you found the sentence: "The archer aims at the dragon with the arrow" in a novel, you might want to draw a parallel between the roles of the various actors (archer, dragon and arrow) in the action and the grammatical function of the corresponding noun in the sentence.

Slate statements are much simpler than English sentences, and always built after the same model (a subject/verb/complements pattern). Therefore, the role of any object in the action is given by its rank in the statement. In our example:

the role of the object archer is: "to come first in the call to #aimAt:with:", and we write this role (1, #aimAt:with:).

the role of the object dragon is: "to come second in the call to #aimAt:with:", which we write (2, #aimAt:with:).

the role of the object arrow is: "to come third in the call to #aimAt:with:", which we write (3, #aimAt:with:).

This has an important consequence: a method is generally not defined on a single object, but on a group of objects.

Multiple Dispatch

Let us take our example further and see how roles influence the way a method is selected by all objects in the tuple. For our method #aimAt:with:, we could have the following uses

archer aimAt: dragon with: arrow.
hunter aimAt: deer with: rifle.
kid aimAt: teacher with: bubbleGum.

Although these examples use the same method signature, you would expect the effects and actions performed by the various objects taking part to the scene to vary: the arrow might hit the dragon without killing it and the archer have to flee from an angry beast; the deer might be killed and the hunter proudly show off in front of his friends; the bubble gum might miss its target and the kid be punished by his teacher. To further emphasize the fact that all objects took part to the action, the following examples would still lead to different consequences for the actors

archer aimAt: dragon with: rifle.
hunter aimAt: deer with: bubbleGum.

Let us go back to the statement: archer aimAt: dragon with: arrow. We will see in a moment how to control on which objects dispatch will happen. Let us suppose as a first step that the dispatch takes place only on the first object. The algorithm used to select the corresponding method resembles single dispatch: archer and its delegates are scanned until one that has a method for the role (1, #aimAt:with:) is found, or all delegates have been unsuccessfully scanned (remember that roles are stored on the objects themselves, just as slots are). The scan takes place on archer first, and then on each of its delegates, starting with the most recently added one. The scan then proceeds "depth-first".

The following figure shows the order in which the objects are queried for the role. Each box represents an object and each arrow represents a delegation slot. For the needs of the picture, each object has an index; objects are queried in the order of their indexes.

Image: Depth-first.png

Figure 1-1. Depth first search through delegates

If the role is found, then the corresponding method is executed. The objects dragon and arrow are passed as arguments to it, and the result, an object, is sent back to the caller (for example, the REPL, if that is where you evaluated the code from). The method executed is the "most specific" method that has the signature #aimAt:with: and applies to archer: if we call delegation distance the number of arcs in the graph of delegates for archer, then the method that is executed has the shortest delegation distance among all methods that could apply.

When scanning the delegates for matching methods, there can be some additional problems. A method may be found several times: in this case, it will be remembered only the first time it is found.

Consider for example the following diagram, where each arrow stands for a delegation slot and each box for an object. Suppose we order the object Eel to swim. The role (1, #swim) is stored on the object Fish. The lookup will find the method corresponding to the role (1, #swim) on Fish twice: once when considering FreshWaterFish delegates, and once when considering SeaFish delegates.

Image: Twice-same-method.png

Figure 1-2. Same method name found twice on delegates

There may also be cycles in the delegation graph. They are detected and avoided. The following graph shows the delegation graph of the Slate object Traits traits (resulting from the execution of the unary method traits on the object Traits). Each arrow stands for a delegation slot, the name of the delegation slot being the same as the arrow label. A method defined on Traits traits and looked up from either Traits traits or Cloneable traits produces a cycle.

Figure 1-3. Cycles in a delegation graph

Let us now suppose in our example that dispatch happens on all objects. All objects participating in the call (archer, dragon and arrow) and their delegates are scanned independently for matching roles. The scan is the same on all objects, and the same as in the single dispatch case: it goes depth first, from the most recently added to the least recently added delegate. The goal of the search is to find methods that can be reached with each of the roles (1, #aimAt:with:), (2, #aimAt:with:), (3, #aimAt:with:), at the same time, in the respective delegation graphs of archer, dragon and arrow.

If at least one such method cannot be found, then a condition is signalled. Otherwise, we may be left with several methods to choose from. The choice is based on the delegation distance: each candidate method is found at some composite delegation distance (d1, d2, d3), where d1 is the delegation distance from archer, d2 the delegation distance from dragon and d3 the delegation distance from arrow. Let us order the methods lexicographically according to their (composite) delegation distances. The method that will be executed is the first according to that order. It is unique in the following sense: given the objects archer, dragon and arrow and the method signature #aimAt:with:, there cannot be a different method with the same delegation distance (d1, d2, d3).

This dispatch scheme has an important property that follows the intuition: the smaller the role of the object, the more specialized for that object the method is. In our case, the roles are, from the smaller to the grater: (1, #aimAt:with:) for archer, (2, #aimAt:with:) for dragon, (3, #aimAt:with:) for arrow. The method that will be executed is more specialized for archer, then for dragon, then for arrow, due to the lexicographic ordering used on delegation distances.

If the role (1, #aimAt:with:) is found on archer and on soldier, and archer delegates to soldier, then the method corresponding to the role on archer will be selected. In the same manner, if the role (2, #aimAt:with:) is found on dragon and beast, and dragon delegates to beast, then the method corresponding to the role on dragon will be given a preference.

When creating a method, you decide:

  • the role of each object, through the signature of the method
  • for which roles dispatch is going to happen
  • where to place the role (and reference the method) in the delegation graph for each object participating in the dispatch

Methods are not directly stored on objects. Instead, Slate uses maps from roles to methods, and to the Slate user, it looks as if each object just remembered its role for each method it can participate in. This is just the model for roles and methods; for efficiency, the implementation may differ.

Single dispatch is just a special case of multiple dispatch, where you send the message on a tuple of objects but perform dispatch only on one of them. One important consequence of multiple dispatch is that the methods you need tend to be shorter than in a single dispatch only scheme, since you already have some knowledge about the objects themselves at the time of the call.

Organizing Objects

The basic object creation protocol in Slate, as in other prototype-based languages such as Self, is to clone or copy an existing object to get a new one, and extend this new object until it has the required functionality.

Let us suppose we have the following object setup:

Image: Before-cloning.png

Figure 1-4. Sample object setup

For the curious, this setup is quite common and is obtained by issuing the following command:

Slate 2> define: #x &parents: {Cloneable}.
("x" )
Slate 3> 

Deriving objects will be described shortly. Suppose we now clone the object x with the command:

Slate 3> addSlot: #y valued: x clone.
lobby
Slate 4> 

The object setup is now as follows:

Image: After-cloning.png

Figure 1-5. Object setup after cloning

Cloning performs a bitwise copy of the old object to produce the new one. The new object then gets the same number of slots and roles, with exactly the same values for these slots and roles.

Each object in Slate has a delegation slot called traits. You can then think of objects as instance/traits pairs (but the relationship between an object and its traits is not that which exists between an instance and its class in other languages). The traits object is used to constitute families of objects. In any family of objects, the trait object is used to encode structure and behavior that is common to all objects in the family. This is parallel to the classical class mechanism, where all instances of a given class share the behavior defined by the class.

To specialize one object in the family, you can just add slots to it, or define new methods on it. The traits object for the family acts as a pool for common behavior, that each object in the family is free to use or not through delegation. In the preceding diagram, any slots or roles that are stored on x traits are shared between the objects x and y, while each of them still has its own sets of slots and roles available for specialization.

While it is possible to globally restrict access to a slot by declaring it with the #addImmutableSlot:valued: method, there is no direct equivalent of this method to restrict access to roles. Controlling access to the roles (and thus restricting or overriding execution of methods) can be done through the use of subjects and layers. In fact, subject and layers offer more generality than the simpler #addImmutableSlot:valued: mechanism, and can be used to restrict both slots access and roles access to certain objects. This reminds the design by contract notion used in Eiffel.

To be cloneable, a Slate object has to have the prototype object Cloneable in its delegation graph.

Cloning is a bitwise object copy, maybe with some precondition checked to allow or disallow the copy to be performed. You may think of cloning as a controlled shallow copy. The role (1, #shallowCopy) is stored on the Root object, making it available for every object in the sys|em, and is defined to select the clone method on that object. Copying performs a deeper copy: it will try to duplicate the values of specific slots (calling copy or clone on them) onto a new object. The creation of the new object relies internally on cloning, so the traits are shared between the object and its copy, but the roles specific to the object are copied over to the new object.

Oddballs

Although the vast majority of objects are cloneable, not all objects must be cloneable. For instance, the True and False objects, which stand for boolean truth and falsehood, must be unique for any logical predicate to work. Another example of an object that should not be cloneable is Nil, which models the absence of any value.

The objects that cannot be cloned are called oddballs, after the name of the prototype which implements the impossibility to clone. Each existing oddball in Slate has this prototype in its delegates' list. For example:

  • The object Nil has 1 slot called traits:
Slate 2> Nil slotNames.
{#traitsWindow}
Slate 3> 
  • Nil delegates to its only slot:
Slate 3> Nil delegateNames.
{#traitsWindow}
Slate 4> 
  • Nil's only delegate is Oddball traits:
Slate 4> Nil traits == Oddball traits.
True
Slate 5>

The clone message is available for any Slate object and always produce a new, bitwise identical copy of its receiver. However, critical core objects such as True, False, and Nil are Oddballs, which cannot be cloned - it is part of their semantics to be unique, and this prevents confusion by the standard libraries about what really is True, for example.

Deriving

When you design your graphs of objects, you will often need sub-families in other families of objects, in order to extend the objects in the sub-families. Although you can achieve this by copying and adding delegation slots, this behavior is encapsulated in one single operation, since it is very common. This operation is called derivation. In the following figure, z derives from x, and is produced by the following expression:

Slate 3> addSlot: #z valued: x derive.
lobby
Slate 4> 

Image: After-deriving.png

Figure 1-6. Object derivation

You can thus create hierarchies of objects, sharing slots and behavior. These hierarchies are of a different nature from what you get with class hierarchies. Instead of walking up a class hierarchy by stating that each subclass "is a kind of" its superclass(es), you walk up hierarchies of objects by stating that each object "is akin to" other members of its family and "stems from" upper-level families. You get thus a kind of genealogy of objects.

This can be further refined, as it is possible for an object to have several delegates, or to be part of several families.

As an exercise in understanding the object relationships, let us explore the connection between Cloneable and Derivable. This can be done by using the object inspector.

[NOTE: The 0.3.5 inspector is broken. Update to current to try these examples.]

Slate 2> load: 'src/lib/inspect.slate'.
Loading 'src/lib/inspect.slate'
Nil
Slate 3> 

This loads the inspector inside the image. Starting the inspection is done as follows:

Slate 3> inspect: Derivable.
("Derivable" )
Slate 4> 

Now we are inside the inspector. All commands we issue are first dispatched to it, until we finish the inspecting session with a close command. For example, let us get the slots and delegates of Derivable.

Slate 4> slots.
{#traitsWindow}
Slate 5> parents.
{#traitsWindow}
Slate 6> 

Let us go and inspect this #traitsWindow slot.

Slate 6> go: #traitsWindow.
("Derivable" traits1: ("Root traits" printName: 'Root traits'). traits: ("Derivable traits" printName: 'Derivable traits'.
        traitsNames: {...}). printName: 'Derivable')
Slate 7> 

To go back to the previous object, the back command may be used. Other commands to rewind contexts are backToStart, which goes back to the object we started inspecting, and back:, which takes an integer as argument and tries to rewind this number of contexts. One last command is it, which will redisplay information about the object being currently inspected.

Inside the inspector, it is possible to run normal Slate code. This can be useful, for example, to make sure if we are inspecting some object or its traits, as they can be a little hard to distinguish at first.

Slate 7> it == Derivable.
False
Slate 8> it == Derivable traitsWindow.
True
Slate 9> 

Slots addition or removal or method manipulation can also be performed from inside the inspector. One just need to be careful that the execution context is the inspector itself, and not the lobby or some other namespace.

The relations between Derivable and Cloneable is then as follows.

Image: Cloneable-derivable.png

Figure 1-7. Cloneable and Derivable

Prototypes and traits

Some objects in the system are abstract: their role in the system is to collect slots and behavior common to other objects. They capture how other objects resemble each other, they are the traits of the other objects.

Suppose you see an elephant for the first time. If you try to model it with an object, you would put all attributes and behavior attached to this new specimen on the object itself. But if you then see another elephant, instead of repeating the whole description of the elephant on this second specimen, you would notice the two are alike, and model this with another object.

That object would not stand for any real elephant, but would hold all that elephants have in common, the traits that make them be elephants. It would be the traits object for elephants, and the two real elephants you met would delegate to this traits object for slots and behavior common to all elephants, while having also slots and behavior to model their specificities, like their age or height.


The ability to easily go from the concrete to the abstract when trying to model a problem is a property of prototype-based object systems. It gives you more freedom as you are not forced to guess abstractions (such as classes) first and then try to match the reality of the problem at hand. Instead, an exploratory style of development is encouraged, just as in the example with the elephants.

Although traits collect slots and behavior for other objects, they are not the same concept as classes in classical class hierarchies. An object is not an instance of its traits objects, nor does it rely solely on its traits objects to get its slots and behavior, as it is possible to define slots and behavior directly on the object itself without using its traits objects.

Creating a traits object is often transparent. When you copy or derive an object, the new object will have a traits slot referencing its traits object. The traits object will be the same as the traits of the initial object in the case of copying, and a new one in the case of deriving.

However, if you need to add a new traits object to an existing object, you will have to clone some abstract object that represents "being a traits object". Such abstract objects that model an intrinsic property of the system are called prototypes in Slate, and they roughly correspond to abstract classes in class hierarchies.

When Slate gets sufficiently advanced to support a meta-object protocol, the Slate object system will be described in terms of itself (as is the case of Smalltalk systems).

Namespaces

Each object is made accessible by its name, and each name, in turn, is defined within a scope: local variables have a meaning only inside the block they belong to; slots have a meaning inside the object that defines them, and so on... Now some objects have to be more globally accessible than that. Suppose you come up with a very useful application. Suppose I load your application in a Slate image, only to find out that some of the names you used for your objects are the same as some I have, but with a different and incompatible implementation.

Instead of requiring the names of objects to be unique across all possible Slate images there can be in the world :-), it is easier to restrict the meaning of the names for more global objects to a certain scope. That scope is called a namespace. I would then load your application inside a namespace specially created for it, so that I can still use it without incurring name clashes with any other part of the system.

A Namespace object is a Slate object whose primary use is to hold other objects as its slots. Since the scope of slots is restricted to the current object, all objects within the Namespace are only accessible from within that Namespace.

The idiomatic way to add a namespace is to use the ensureNamespace: method. This method is defined on the Root object's traits, making it usable from anywhere in Slate.

Slate 2> ensureNamespace: #MyRoom.
(traitsWindow)
Slate 3> MyRoom traitsWindow == Namespace traitsWindow.
True
Slate 4> 

A Namespace clone called MyRoom was added to the lobby. To fill a namespace, all you have to do is use the #addSlot:valued: method.

Slate 4> MyRoom addSlot: #bag valued: Bag newEmpty.
(traitsWindow. bag)
Slate 5> 

Trying to reach for the bag slot directly from the lobby yields an error, since that slot is encapsulated within the MyRoom namespace.

Slate 5> bag.
The method #bag was not found for the following arguments:
{lobby}
Nil
Slate 6> 

The bag slot can be accessed using the expression: MyRoom bag. This expression selects the accessor method for the bag slot of the MyRoom object. This accessor was automatically created by the addSlot:valued: method we used to create the slot.

Sometimes it is desirable to directly access all slots present in a namespace without having to reference it first. This can me accomplished by using the &delegate: option (available in post-0.3.5, previously as ensureDelegatedNamespace:), which modifies the use of the ensureNamespace: method, by linking the new namespace with a delegation slot for the enclosing object.

Slate 6> MyRoom ensureNamespace: #Desk &delegate: True.
(traitsWindow)
Slate 7> MyRoom Desk addSlot: #book valued: 'Dune'.
(traitsWindow. book)
Slate 8> MyRoom book.
'Dune'
Slate 9> 

In the last expression, the accessor #book was not found on the object MyRoom, and the method lookup algorithm forwarded the request to the most recently added delegate for MyRoom, namely the namespace Desk. The accessor was found there and executed. Since the usual lookup algorithm is used for namespace delegation, it is necessary to be extra careful when using delegated namespaces, since this destroys one level of name encapsulation.

Slate 9> MyRoom ensureNamespace: #Table &delegate: True.
(traitsWindow)
Slate 10> MyRoom Table addSlot: #book valued: 'East of Eden'.
(traitsWindow. book)
Slate 11> MyRoom book.
'East of Eden'
Slate 12> 

Organizing Behavior


Declaring Methods

Let us see how to define methods in Slate through a series of examples. As a first step, we will define a method with no arguments, stored on one specific object.

Slate 1> addPrototype: #cat &parents: {Cloneable}.
("cat" )
Slate 2> _@cat meow [Console ; 'Meeoooowww'].
[meow]

On line 1, we create an object called cat derived from the prototype Cloneable. On the following line, we get the result of the creation: the lobby, with the slot cat added. The line 2 is the definition of a method called meow on the object cat and the last line, the result of the creation.

Let us first look at the right part of the method definition, the method body. It is made of one Slate statement: the object Console which represents the standard input/output terminal, the literal string selects the method used for stream serialization and invokes it on the Console. #; is the Slate equivalent of the operator<< method in C++ or the use of the print >> construct in Python.

Note that there is no dot at the end of the statement, so that the method returns the object resulting from that statement. Had we put a dot, the object resulting from the statement would have been discarded, and Nil returned instead. Fortunately there is a wrapper which does this for you, and lets you forget about the Console; it is called inform: and is used as follows:

Slate 3> _@cat meow [inform: 'Meeoooowww'].
[meow]

The method body is enclosed in square brackets, making it a literal code block. Code blocks are full objects that encapsulate the execution of statements. Code blocks can be used directly at the REPL; they respond to the messages #do, #applyWith:, ... depending on the number of arguments they accept. The block we appears to take no argument, but in fact the names in the signature line are inputs that can be accessed by their names - except for the underscore here which silently “eats” inputs.

Had we wanted more statements in our method, we would have separated them with dots. The only place inside a code block where a dot is optional is after the end of the last statement in the block, because the last statement gives the return value of the block (the value of the last statement without a dot, and Nil otherwise, the value of an empty statement).

The left part of the method definition sets up the following properties for the method:

  • for which roles dispatch is required,
  • on which objects to store the roles,
  • under which name the object is used inside the method's body.

This is set up through the use of the @ syntax. On the left side of the @ sign, there is either a symbol that will be bound, in the method's body, to the object receiving the message, or the _ sign, meaning that no symbol needs to be bound. On the right side, there is an expression which evaluates to the object on which that role for that method will be stored. If there is no such annotation, then no role link is created; of course, at least one annotation must exist for the method to be remembered and used at all!

In our example, the object is not used inside the method's body, so we do not bind it to a variable. Furthermore, the role will be stored on our cat object. Let us now experiment with this method:

Slate 6> cat meow.
Meeoooowww
Nil
Slate 7> addSlot: #kitten valued: cat copy.
lobby
Slate 8> kitten meow.
Meeoooowww
Nil
Slate 9> _@cat purr [inform: 'rrrrrrnn'].
[purr]
Slate 10> kitten purr.
The method #purr was not found for the following arguments:
{("cat" )}
Nil

Slate 11> _@(cat traits) purr [inform: '...\n'].
[purr]
Slate 12> kitten purr.
...

Nil
Slate 13> 

The @ can be placed or omitted for any object playing a role in the execution of the method to better suit your needs, as long as there remains at least one such annotation to create a role. In the above example, had we omitted it, no information on where to store the role would have been left, and the method would have been forgotten entirely by the system.

This syntax is just a workaround to declare methods. There will be a more friendly solution when Slate has a graphical user interface and browsers are developed.


One last thing there is to know about method declarations is how to control where dispatch happen. The @ syntax is optional in method declarations, although it is mandatory when only one role is used (otherwise no object would be selected to store the role). Thus, going back to our beast hunting example, the line:

archer@(Soldier traits) aimAt: dragon@(Beast traits) with: arrow

stores the role (1, #aimAt:with:) on the object resulting from the evaluation of Soldier traits, the role (2, #aimAt:with:) on the object resulting from the evaluation of Beast traits. No dispatch happens on arrow.

When no @ sign is used, Slate behaves as if the corresponding role was stored on the object called NoRole, which models the fact that an argument position (and the object passed in to it) does not play a role in a method invocation. So conceptually, dispatch happens on all objects taking part in a method invocation, possibly with some roles bound to NoRole when the dispatch should not have any effect. For efficiency reasons, the implementation may differ, while still supporting this model for reasoning about roles.

Slate 2> addPrototype: #meat derivedFrom: {Cloneable}.
("meat" )
Slate 3> _@meat cookWith: _@(String traits) [inform: 'Mmm...'].
[cookWith:]
Slate 4> _@meat cookWith: _@NoRole [inform: 'Yuck!'].
[cookWith:]
Slate 5> meat cookWith: 'onions'.
Mmm...
Nil
Slate 6> meat cookWith: Nil.
Yuck!
Nil
Slate 7> _@meat cookWith: _ [inform: 'Try harder'].
[cookWith:]
Slate 8> meat cookWith: Nil.
Try harder
Nil
Slate 9> meat cookWith: 'eggs'.
Mmm...
Nil
Slate 10> 

Methods in Slate, although similar in intention to the generic functions used in CLOS (for example), have the following conceptual advantage over them: since the keys to method selection, the roles, are an intrinsic property of Slate objects, the definition of methods preserves the object encapsulation.

Method Introspection

Since Slate has the special notion of a role for linking objects to methods that apply to them, there is a specific protocol for querying them separate from that for slots.

Finding Methods

To find methods that apply to a specific object in a certain role position, you just ask it for its methodsAt: the index. To find the methods that in traditional single-dispatch languages "belong" to an object, you can enter object methodsAt: 0. Entering this for the lobby shows global scripts and accessors, for example:

Slate> methodsAt: 0.
{[it4]. [nextObjectAfter:delegatingTo:]. [quit]. [firstObjectReferringTo:].
    [doubleConfirm:]. [nextObjectAfter:referringTo:]. [inspect:]. [isLastObject:].
    [currentModule]. [isLastObject:delegatingTo:]. [kitten]. [isLastObject:referringTo:].
    [firstObjectDelegatingTo:]. [it2]. [overrideThis]. [Types]. ...}.

This won't tell you what methods an object inherits, but it's an important first step to getting a handle on Slate methods.

Note: the contents of what's between the brackets when Slate prints out a method object is not the source code of the method, it's the selector. De-compilation of methods is not done automatically in Slate, and even though many methods know their sources, sources are often not enough to understand a method, and can be verbose. So for now, there is a compromise in printing for brevity's sake.

So, let's say we want to find a method, and we know what name it has. The quickest way assumes we have some example argument objects that we want to use it with, or ones that we saw in some other working or non-working code. We take the name of that method, the literal symbol selector, and we ask Slate to find a method on some argument array, like so:

Slate 1> #+ findOn: {3. 4}.
[+]

which finds integer arithmetic, or so we assume. If we want to know for sure, we can ask the method for its signature, which is an object uniquely identifying a method installation (roles + selector):

Slate 2> it signature.
("Signature" selector: #'+'. roles: {("SmallInteger traits" ...). ("SmallInteger traits" ...)})

This one takes quite a bit longer, because Slate objects point to their installed methods and not vice versa. Now we know that we're looking at the integer arithmetic routine; it happens to be primitive, so don't look too closely, though! In the future, the method decompilation and source may be used to speed up this process, but it will not be trivially cheap. However, it does work very well, since it looks for the exact method object. Another similar method is roleHolders, which will return just the roles part of the signature.

Another way in which Slate differs from single-dispatch languages is that a single object can refer to several methods implementating the same selector; the queries methodsNamed: and methodsNamed:at: help collect all of these.

Finally, an object can be asked for all of its methods at once with the methods query.

But let's back up a minute and start again just with the symbols. Can we just ask for all the methods with a selector for their name, without knowing anything else? As it happens, we can; we just ask a symbol for its implementations, which searches the loaded Slate world:

Slate 1> #size implementations.
{"Set" [size]. [size]. [size]. [size]. [size]. [size]. [size]. [size]. [size].
    [size]. [size]. [size]. [size]. [size]. [size]. [size]. ...}.

Okay, so we've got a comprehensive bunch of methods there with the same name. There's not much we can do with this yet, but later tools can be combined to process these smartly.

Who's calling Whom?

So, we have ways to get methods, but we'd like to know something about what they do. The quick way to get this (even without a source tree!) is to ask a method for allSelectorsSent:

Slate 2> (#isNegative findOn: {3}) allSelectorsSent.
{#'<'. #zero}

Now, you say, wouldn't it be even nicer if we knew every place that sends a certain message? There's also a way to do that (called callers in 0.3.5 and earlier):

Slate 3> #size senders.
{"Set" [beginsWith:]. [endsWith:]. [at:insertAll:]. [isAtEnd]. [next]. [subtract:].
    [reversed]. [atWrap:put:]. [indexMiddle]. [allButLast:]. [opcodeIndicesDo:]. [parseExpression].
    [gapSize]. [union:]. [;]. [includesKey:]. ...}.

You can also narrow a search for callers to an individual object, invoking methodsSending: (called methodsCalling: in 0.3.5 and earlier) just as in the previous section.

Maps

Slate objects appear to be self-descriptive, but for efficiency's sake, similar objects and clones share common descriptors called a map. Each object may be sent the _map message to examine the map as a whole, showing the object's roles and slots table. For the most part, however, this feature may be safely ignored - just don't try to assign to #_map unless you know what you're doing!