In the previous chapter, the coding convention and message execution order were discuessed. This chapter concentrates on the Smalltalk grammer or syntax. There are several rules that govern the way Smalltalk statements are written. A Smalltalk statement, in addition to containing messages, may also contain an assignment statement (setting the contents of a variable to specific value), or a return expression.
Each statement is separated by a period except the last statement. If a statement has more than one expression, the statement is executed according to the message execution order discuessed in the previous chapter (See "Order of Message Execution".)
Recall from the previous chapter that when Smalltalk processes each message, the statement in the message is replaced by a return value which is the result of that executing that message. The following is an example of the method name:address: for the Student class:
name: aName address: anAddress self name: aName. self address: anAddress
Note that the first line of a method is the interface definition and it does not end with a period. The second line is a executable statement and it ends with a period. The final line does not need a period because it is the end of the method.
Periods are optional for the last statement in a method. Smalltalk separates executable statements by looking for a period. As a result, multiple statements can appear on one line, or one statement can appear on many lines. The previous example can be written as follows:
name: aName Student: anAddress self name: aName. self address: anAddress
To make the code easier to read, it is wise to have no more than one statement on one line. Blank lines are ingnored by Smalltalk.
The syntax for assigment statement is:
variable := statement
where variable can be a name within the scope of the method and statement can be any valid Smalltalk statement.
x := 7. k := `some character'. aVariable := j * k.
In Smalltalk, a message will always return a value. The default return value is the receiver object itself. A method can override this default return value by placing a caret (^) symbol in front of a statement. When a ^ is encountered, the method ends execution and returns the value of the statement following the ^ return symbol.
The result of statement is returned, where statement can be any valid statement.
A return expression normally appears in front of the last statement in a method or in front of a statement that executes as part of a condition statment. This practice is necessary because the return expression ends the execution ot the method in which it is found.
Example 3.1: Returning a value
y := y + 7. ^y "return the value of y"
The statement also can be written as:
^y := y + 7. "return the value of y"
To return the value of y + 7 without changing value of y:
^y + 7 "return value of y + 7 but does not change value of y"
Example 3.2: Returning a value from a conditional statement
a < b ifTrue: [^a] ifFalse:[^b]
This example contains more than one return statement. This code returns the value of a if a is greater than b, otherwise it returns the value of b. Conditional statements will be discussed in greater detail in a later chapter.
In the statement ^a, the expression is the variable a and the returned value of the expression is the value of a. The value of a statement is always equivalent to the last expression executed in the statement.
For statements that has more than one message, the return value equals the value from the last message executed.
^Student new name: aPerson name
Recall form the previous chapter that unary messages are evaluated first, followed by binary messages, then keyword messages. (See "Order of Message Execution"). The statement above is evaluated in the following order:
(2) the name message is sent to the object pointed to by the variable aPerson
(3) the name: message is sent to the result of the new message.
The value of the statement will be the value from the name: message, because it is the last message executed. The name: message does not specify a return value so it returns the default value. What is the default value? The default value is always self which is the receiver of the message. Therefore, in this example the return value is the Student instance created by the new message (the receiver of the name: message).
In Smalltalk, comments are enclosed in double quotations, such as:
"this is a comment in Smalltalk"
It is an accepted convention to have comments at the beginning of a method to explain its behavior, including its return value.
name: aName address: adAddress "Set the receiver's name and address to the specified values." self name: aName. self address: anAddress name "Return the student name for the receiver." ^name
Smalltalk ignores all comments when scanning a method for execution. This means that a comment can appead anyway as long as it is placed within two double quatations. Placing a period at the end of a comment causes Smalltalk to associate that period with the expression preceding the comment.
name: aName address: adAddress "Comment with the period outside". (extra period) self name: aName. self address: anAddress
Smalltalk read as:
name: aName address: adAddress. (extra period) self name: aName. self address: anAddress
This will cause a compilation error because the first line of a method is the interface definition and it does not end with a period. (See Statements).
Temporary variables are defined at the method interface definition. For example, the statement:
name: aName address: anAddress
defines two temporary variables: aName and anAddress. These temporary variables hold the arguments passed into a name:address: message.
A method can add additional temporary variables by listing the variable name enclosed in vertical bars.
| newName newAddress |
This statement does not end with period.
The name of a temporary variable starts with a lowercase letter and must be unique within the method. This means it cannot duplicate the name of an instance variable, a temporary variable defined in the interface, or another temporary variable.
Following is an exmple of a method:
aSampleMethod: someInput "This is just an example of method." | newValue | newValue := someInput * 2. ^newValue
This method does not have much meaning other than to illustrate a basic method layout.
This method does not really contain efficient code, however. It defines a variable to hold the result of the multiplication and then uses that temporary variable only in the return statement. The method can be simplified as follow:
aSampleMethod: someInput "This is just an example of method." ^someInput * 5
The return expression first processes all statement that appear to its right and then returns the value from the last expression executed. In this example the result of the multiplication message is the return value.
Blocks are square brackets, contain zero or more expression, and enclose code for looping or conditional execution. For example, a block of code is used to specify what to execute as the result of a true or false conditions:
ifTrue: [x := 2] ifFalse: [x := 5]
You may think of a block as a mini-method within a method. As a result, the following rules apply to blocks:
Because a block is part of a method, it does not have a method interface definition. The block in the following statement:
ifTrue: [x := 2]
is called a zero-argument block; it cannot accept any argument. However, it is possible to define a block that can take arguments such as the following:
[:varaible1 | code] [:variable1 | :variable2 | code]
where variable1 and variable2 are temporary variable names and are valid only within the scope of the block. The variable name is proceded by a colon.
[:aNumber | x * aNumber]
The varaible aNumber is defined within the block. This statement then multiplies a variable named x by the argument passed into the block. The variable x must be in the scope of the method in which the block resides.
More details about messages that require blocks as argument will be given in a later chapter. Further information about blocks will also be discuss in that chapter.
Sometimes it is necessary to send one object several consecutive messages. As an example, let's review the following code:
Example 3.3: Cascaded messages
name: aName address: anAddress phoneNumber: aPhoneNumber "set the name, address, and phone number for this student." self name: aName. self address: anAddress. self phoneNumber: aPhoneNumber
Self is the receiver of the messages for all of this method's statement. In order to execute the messages correctly, they need to appear in separate statements. Let's examine what happens when self is followed by all these keyword messages without separation:
self name: aName address: anAddress phoneNumber: aPhoneNumber.
Since Smalltalk allows the grouping of all keywords together in one expression, the message name:address:phoneNumber would be sent to the receiver as a valid expression. However, this will cause an infinite number of recursive calls. To avoid this each keyword must be in a separate statement.
If the three statements are combined into one with self in front of each keyword as follows:
self name: aName self address: anAddress self phoneNumber: aPhoneNumber.
Smalltalk evaluates the second self in the statement as a message sent to the object aName. It evaluates the last self as a message sent to the object anAddress. This causes an execution error since self is not a supported message by either of those two objects.
There is a coding shortcut deals with consecutive statements. The first statement is written normanlly but all successive statements can omit the receiving object. Each statement ends with a semicolon (;) rather than a period., except the last statement for which the period is optional. The method in Example 3.3 can be written as the following:
name: aName address: anAddress phoneNumber: aPhoneNumber "Set the name, address, and phone number of the student." self name: aName; address: anAddress; phoneNumber: aPhoneNumber
The first statement is normal except that it ends with a semicolon. The object receiving the last message in this sequence becomes the object receiving the first message in the next statement.
By encapsulating instance variable within a class, changes can be made to specific parts of the class without affecting other parts of the class. This is archived with the help of two methods called get and set. These methods are the only way to access a class' variables. A set method exists to provide a way to change the value of a class variable to a passed-in value. The get method provides a way to have the receiver return a variable value to the sending object.
For example, the Student object has three get and set methods, one for each of its instance variables. They are
name "gets the value of name" address "gets the value of address" phoneNumber "gets the value of phoneNumber" name:aName "sets name to the value of aName" address:anAddress "sets anAddress to the value of anAddress" phoneNumber:aPhoneNumber "sets phoneNumber to the value of aPhoneNumber"
Additional methods can be created from these getters and setters to combine their capabilities:
name:address: "set name and address in one message" name:address:phoneNumber: "set name, address, and phoneNumber"
Getter and setter methods provide to users a common, stable interface to a class' attributes (variables). Because the implementation of these methods is hidden from users, any change to their internal processing is never a problem as long as their interface remains the same.
We have discussed the following in this chapter: