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.

1> addPrototype: #cat derivedFrom: {Cloneable}.
("cat" traits: ("cat" traits: ("Traits" ...). printName: 'cat'. parent0: ("Cloneable" ...)))
2> _@cat meow [Console ; 'Meeoooowww\n'].
[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 output stream, and the literal string 'Meeoooowww', are sent the binary message #; which selects the method used for stream serialization. It 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.

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 used takes no argument.

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 left part of the method definition sets up the following properties for the method:

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 the role for that method will be stored.

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
("WriteStream" isBinary: False. Exhaustion: ("Exhaustion" exitContinuation: []. stream: Nil. returnContinuation: [].
                handlers: {"ExtensibleArray"}. traits: ("Exhaustion" ...)). indexOfLastDirtyElement: 0. Condition: ("Condition" exitContinuation: [].
                stream: Nil. returnContinuation: []. handlers: {"ExtensibleArray"}. traits: ("Condition" ...)).
        resource: ("ExternalResource" writeStream: ("PrettyPrinter" ...). locator: Nil. traits: ("ExternalResource" ...). interactor: ("ReadWriteStream" ...).
                handle: Nil. readStream: ("ReadStream" ...)). traits: ("WriteStream" printName: 'WriteStream'. parent0: ("Stream" ...).
                parent1: ("WriteStream" ...). traits: ("Traits" ...)))
Slate 7> addSlot: #kitten valued: cat copy.
(cat . prototypes . globals . kitten .
        Mixins . Types . VM . traits )
Slate 8> kitten meow.
Meeoooowww
("WriteStream" isBinary: False. Exhaustion: ("Exhaustion" exitContinuation: []. stream: Nil. returnContinuation: [].
                handlers: {"ExtensibleArray"}. traits: ("Exhaustion" ...)). indexOfLastDirtyElement: 0. Condition: ("Condition" exitContinuation: [].
                stream: Nil. returnContinuation: []. handlers: {"ExtensibleArray"}. traits: ("Condition" ...)).
        resource: ("ExternalResource" writeStream: ("PrettyPrinter" ...). locator: Nil. traits: ("ExternalResource" ...). interactor: ("ReadWriteStream" ...).
                handle: Nil. readStream: ("ReadStream" ...)). traits: ("WriteStream" printName: 'WriteStream'. parent0: ("Stream" ...).
                parent1: ("WriteStream" ...). traits: ("Traits" ...)))
Slate 9> _@cat purr [Console ; 'rrrrrrnn'].
[purr]
Slate 10> kitten purr.
The following condition was signaled:
The method #purr was not found for the following arguments:
{("cat" traits: ("cat" ...))}

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

Slate 11> _@(cat traits) purr [Console ; '...\n'].
[purr]
Slate 12> kitten purr.
...
("WriteStream" isBinary: False. Exhaustion: ("Exhaustion" exitContinuation: []. stream: Nil. returnContinuation: [].
                handlers: {"ExtensibleArray"}. traits: ("Exhaustion" ...)). indexOfLastDirtyElement: 0. Condition: ("Condition" exitContinuation: [].
                stream: Nil. returnContinuation: []. handlers: {"ExtensibleArray"}. traits: ("Condition" ...)).
        resource: ("ExternalResource" writeStream: ("PrettyPrinter" ...). locator: Nil. traits: ("ExternalResource" ...). interactor: ("ReadWriteStream" ...).
                handle: Nil. readStream: ("ReadStream" ...)). traits: ("WriteStream" printName: 'WriteStream'. parent0: ("Stream" ...).
                parent1: ("WriteStream" ...). traits: ("Traits" ...)))
Slate 13> 

The @ sign is optional, and 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 information on where to store the roles. In the above example, had we omitted it, no information on where to store the role would have been left.

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.

Method introspection is still in progress. Each object may be sent the #map message to retrieve its roles and slots table.

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.

Note

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 object 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" traits: ("meat" traits: ("Traits" ...). printName: 'meat'. parent0: ("Cloneable" ...)))
Slate 3> _@meat cookWith: _@(String traits) [Console ; 'Mmm...\n'].
[cookWith:]
Slate 4> _@meat cookWith: _@NoRole [Console ; 'Yuck!\n'].
[cookWith:]
Slate 5> meat cookWith: 'onions'.
Mmm...
("WriteStream" isBinary: False. Exhaustion: ("Exhaustion" exitContinuation: []. stream: Nil. returnContinuation: [].
                handlers: {"ExtensibleArray"}. traits: ("Exhaustion" ...)). indexOfLastDirtyElement: 0. Condition: ("Condition" exitContinuation: [].
                stream: Nil. returnContinuation: []. handlers: {"ExtensibleArray"}. traits: ("Condition" ...)).
        resource: ("ExternalResource" writeStream: ("PrettyPrinter" ...). locator: Nil. traits: ("ExternalResource" ...). interactor: ("ReadWriteStream" ...).
                handle: Nil. readStream: ("ReadStream" ...)). traits: ("WriteStream" printName: 'WriteStream'. parent0: ("Stream" ...).
                parent1: ("WriteStream" ...). traits: ("Traits" ...)))
Slate 6> meat cookWith: Nil.
Yuck!
("WriteStream" isBinary: False. Exhaustion: ("Exhaustion" exitContinuation: []. stream: Nil. returnContinuation: [].
                handlers: {"ExtensibleArray"}. traits: ("Exhaustion" ...)). indexOfLastDirtyElement: 0. Condition: ("Condition" exitContinuation: [].
                stream: Nil. returnContinuation: []. handlers: {"ExtensibleArray"}. traits: ("Condition" ...)).
        resource: ("ExternalResource" writeStream: ("PrettyPrinter" ...). locator: Nil. traits: ("ExternalResource" ...). interactor: ("ReadWriteStream" ...).
                handle: Nil. readStream: ("ReadStream" ...)). traits: ("WriteStream" printName: 'WriteStream'. parent0: ("Stream" ...).
                parent1: ("WriteStream" ...). traits: ("Traits" ...)))
Slate 7> _@meat cookWith: _ [Console ; 'Try harder\n'].
[cookWith:]
Slate 8> meat cookWith: Nil.
Try harder
("WriteStream" isBinary: False. Exhaustion: ("Exhaustion" exitContinuation: []. stream: Nil. returnContinuation: [].
                handlers: {"ExtensibleArray"}. traits: ("Exhaustion" ...)). indexOfLastDirtyElement: 0. Condition: ("Condition" exitContinuation: [].
                stream: Nil. returnContinuation: []. handlers: {"ExtensibleArray"}. traits: ("Condition" ...)).
        resource: ("ExternalResource" writeStream: ("PrettyPrinter" ...). locator: Nil. traits: ("ExternalResource" ...). interactor: ("ReadWriteStream" ...).
                handle: Nil. readStream: ("ReadStream" ...)). traits: ("WriteStream" printName: 'WriteStream'. parent0: ("Stream" ...).
                parent1: ("WriteStream" ...). traits: ("Traits" ...)))
Slate 9> meat cookWith: 'eggs'.
Mmm...
("WriteStream" isBinary: False. Exhaustion: ("Exhaustion" exitContinuation: []. stream: Nil. returnContinuation: [].
                handlers: {"ExtensibleArray"}. traits: ("Exhaustion" ...)). indexOfLastDirtyElement: 0. Condition: ("Condition" exitContinuation: [].
                stream: Nil. returnContinuation: []. handlers: {"ExtensibleArray"}. traits: ("Condition" ...)).
        resource: ("ExternalResource" writeStream: ("PrettyPrinter" ...). locator: Nil. traits: ("ExternalResource" ...). interactor: ("ReadWriteStream" ...).
                handle: Nil. readStream: ("ReadStream" ...)). traits: ("WriteStream" printName: 'WriteStream'. parent0: ("Stream" ...).
                parent1: ("WriteStream" ...). traits: ("Traits" ...)))
Slate 10> 

Note

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.