next up previous contents index
Next: 2.2 Sending Messages Up: 2 Language Reference Previous: 2 Language Reference   Contents   Index

Subsections

2.1 Objects

OBJECTS are fundamental in Slate; everything in a running Slate system consists of objects. Slate objects consist of a number of slots and roles: slots are mappings from Symbols to other objects, and roles are a means of organizing code that can act on the object. Slots themselves are accessed and updated by a kind of message-send which is not distinguishable from other message-sends syntactically, but have some important differences.

Objects in Slate are created by cloning existing objects, rather than instantiating a class. When an object is cloned, the created object has the same slots and values as the original one. The new object will also have the access and update methods for those slots carried over to the new object. Other methods defined on the object will propagate through an analogue of a slot called a role, explained in section 2.4 on Methods.

Both control flow and methods are implemented by specialized objects called blocks, which are code closures. These code closures contain their own slots and create activation objects to handle run-time context when invoked. They can also be stored in slots and sent their own kinds of messages.

2.1.1 Code Blocks

A code block is an object representing an encapsulable context of execution, containing local variables, input variables, the capability to execute expressions sequentially, and finally answers a value to its point of invocation. The default return value for a block is the last expression's value; an early return via ^ (see sub:Early-Returns) can override this.

Blocks have a special syntax for building them up syntactically. Block expressions are delimited by square brackets. The block's optional header can specify input and local slots between vertical bars (||), and then a sequence of expressions which comprises the block's body. The input syntax allows specification of the slot names desired at the beginning. For example,

Slate> [| :i j k | j: 4. k: 5. j + k - i]. 
[]
creates and returns a new block. Within the header, identifiers that begin with a colon such as :i above are parsed as input slots. The order in which they are specified is the order that arguments matching them must be passed in later to evaluate the block. If the block is evaluated later, it will return the expression after the final stop (the period) within the brackets, j + k - i. In this block, i is an input slot, and j and k are local slots which are assigned to and then used in a following expression. The order of specifying the mix of input and local slots does not affect the semantics, but the order of the input slots directly determines what order arguments need to be passed to the block to assign them to the correct slots.

Using the term "slot" for local and input variables is not idle: the block is an actual object with slots for each of these variables, and accessors defined on them which are even callable from outside the block, considering it as an object.

In order to invoke a block, the caller must know how many and in what order it takes input arguments. Every block responds to applyTo:, which takes an array of the input values as its other argument. The block is immediately evaluated, and the result of the evaluation is the block's execution result. For example,

Slate> [| :x :y | x quo: y] applyWith: 17 with: 5. 
3 
Slate> [| :a :b :c | (b raisedTo: 2) - (4 * a * c)]  
 applyTo: {3. 4. 5}. 
-44
Arguments can also be passed in using some easier messages. Blocks that don't expect any inputs respond to do, as follows:

Slate> [| a b | a: 4. b: 5. a + b] do. 
9
Blocks that take one, two, or three inputs, each have special messages applyWith:, applyWith:with:, and applyWith:with:with: which pass in the inputs in the order they were declared in the block header.

If a block is empty, contains an empty body, or the final expression is terminated with a period, it returns Nil when evaluated:

Slate> [] do. 
Nil 
Slate> [| :a :b |] applyTo: {0. 2}. 
Nil 
Slate> [3. 4.] do. 
Nil
If more arguments are passed than the block expects as inputs, an error is raised unless the block specifies a single ``rest'' argument parameter, using an asterisk prefix for the argument identifier in its header (e.g. *rest), in which case the slot is bound to an array of the inputs not otherwise bound.

Blocks furthermore have the property that, although they are a piece of code and the values they access may change between defining the closure and invoking it, the code will ``remember'' what objects it depends on, regardless of what context it may be passed to as a slot value. It is called a lexical closure since it ``closes over'' the environment and variables used in its definition, the lexical context where it was born. This is critical for implementing good control structures in Slate, as is explained later. Basically a block is an activation of its code composed with an environment that can be saved and invoked (perhaps multiple times) long after it is created, and always do so in the way that it reads where it was defined.

2.1.2 Slots

Slots may be mutable or immutable, and explicit slots or delegation (inheritance) slots. These four possibilities are covered by primitive methods defined on all objects.

Slate provides several primitive messages to manage DATA SLOTS (or non-delegating slots):

obj addSlot: slotSymbol
adds a slot using the Symbol as its name, initialized to Nil.
obj addSlot: slotSymbol valued: val
adds a slot under the given name and initializes its value to the given one.
addImmutableSlot:valued:
performs the same as the above method, only without installing a mutator method.
obj removeSlot: slotSymbol
removes the slot with the given name on the object directly and returns whatever value it had.
The effect of all of the slot addition methods when a slot of the same name is present is to update the value and attributes of the slot rather than duplicate or perform nothing.

2.1.3 Inheritance

Slate's means of sharing and conferring behavior involves the use of a DELEGATION SLOT, whereby each object can name and access each object it inherits from. These slots also behave as ordinary slots; messages can access and update their values just as ordinary slots, and the slots can be removed as normal with the removeSlot: message.

The slots' inheritance role involves a precedence order which affects the lookup of messages; from the object's perspective, delegate slots most recently added (not most-recently-set) take more precedence. Section sub:Lookup-Semantics explains the details of lookup semantics.

The relevant primitives specific to delegation slots are:

obj addDelegate: slotSymbol
and obj addDelegate: slotSymbol valued: val add a delegation slot, and also initialize it, respectively. It is recommended to use the latter since delegation to Nil is usually not intended.
addImmutableDelegate:valued:
performs the same as above, without installing a mutator method.
addDelegate:before:valued:
and addDelegate:after:valued: allow for finer control over manipulating delegates, since they explicitly manipulate the precedence order of the delegation slots as they work. Both arguments are slot names, so must be Symbols.


next up previous contents index
Next: 2.2 Sending Messages Up: 2 Language Reference Previous: 2 Language Reference   Contents   Index
Brian Rice 2005-11-21