A Calculator in Pharo Smalltalk Using Spec
A Calculator in Pharo Smalltalk Using Spec
A Calculator in Pharo Smalltalk Using Spec
Chapter 1
1.1
Introduction
To construct our GUI we need to know what Spec expects to find in the class
we will be creating. There are certain selectors and methods that have to be
present at instance and class side and that will be used by Spec when we do
Specalc new openWithSpec.
Basically, at the instance side of the class we will declare the widgets that
we will be using, some aspects of these widgets like labels, actions, states,
and the logic of the application and the glue between these widgets. On the
other hand, at class side, we will set up the basic design of our GUI -how the
widgets are laid out- and its title.
Summarizing:
instance side: widget instantiation along with actions performed by
these and application logic.
class side: widget layout i.e., how all our widgets will be laid out in a
window.
So let us start by analyzing the widgets we will be using, how to glue
them together and adjust them to get a usable GUI.
1.2
The widgets
poolDictionaries: ''
category: 'Specalc'
The same happens here, accept all buttonX as instance variables and defineDigitButtons as a new selector.
This is a long method, but its very simple to understand because once
we have grasped the first button, the rest is similar. So here is what each of
these lines do. First we had the object instantiated in the previous methods:
thats button0. If you follow the sequence of messages its a cascade sent to
the button0 object.
We tell button0 to display some label on it, using the label: message with
a string as argument -in this case 0. The expression button1 label:1 will obviously make the button show the digit 1 as label. Next is another selector:
action: followed by a block.
Lets analyze what this block does. Recall that we instantiated a display
object from Specs LabelModel class which is actually the screen of our calculator. Following the precedence of messages, the first evaluated expression
is: (display label,9) We are sending a message to our display to retrieve the
contents of it and concatenate the string 9. Why? Basically because our
display object might content some string of numbers previously entered and
we want to put the newly pressed number behind of whats already on the
screen. Then you noticed that we send the message asNumber, the purpose
of this, is to avoid entering zeros that are not significant. When we convert
our string to a number the non-relevant zeros are discarded otherwise we
will have a calculator that will show awkward numbers like 000234, when
only 234 should be displayed. Finally we convert that back to a string and
pass this as an argument to the label: selector of our display. The result is
that the display now contains what was previously there plus the content of
our recently pushed button.
The last message we send is state: which accepts as an argument a
boolean. We set it to true to avoid having to double click on each button
to have a value entered on the display. You will notice when buttons are active because they show a pale blue shadow. Try setting the state to false and
you will see what annoying is to have to press two times each button.
SD: we should check with ben because this look strange to me. The name
is strange.
IS: To what name are your referring?.
A comment on style is appropriate here. The exposed method could have
been written in a much more elegant and simple way, making the digit button object pass its label instead of hard-coding the string 1 or 2, etc. However, we choose not to do it that way to keep it easier to understand and
follow.
Now is turn to format the other buttons. This is a little more tricky but
no rocket science at all. Lets start by taking a look at the method, and then
define one method per action and analyse it.
This method all it does is send a message to self for each of the remaning
buttons that are not digit buttons. Categorize these two methods under the
button-defintion protocol.
We will begin with the basic arithmetic operations: addition, subtraction,
multiplication, and division; which will be performed by our buttonPlus, buttonMinus, buttonMult, buttonDiv respectively.
The first line stores the current content of our display object into an instance variable we named previousNumber, as a number so its ready to be
used by the basic binary arithmetic selectors. Then we store the character
literal passed as argument to the operation: selector and store it into another
instance variable called currentOperation. And finally we reset the display
making it ready to receive another string: remember that the display of our
calculator shows strings. Up to here, we have an instance variable that holds
our previously entered number and an instance variable that holds a character literal that will inform our model the operation we will perform once the
second number is entered.
So lets go back to the rest of the arithmetic operations which methods
are similar to addition.
Specalc>>subtraction
buttonMinus
label: '-';
action: [ self operation: $- ];
state: true
Specalc>>multiplication
buttonMult
label: 'x';
action: [ self operation: $x ];
state: true.
Specalc>>division
buttonDiv
label: '/';
action: [ self operation: $/ ];
state: true.
So enter the four basic arithmetic methods, the operation: method, and
categorise them under the operations protocol
Now lets focus on those operations that do not require more than a number and a button press such as obtaining the square root, changing the sign,
and getting the logarithm of an entered number.
Specalc>>changeSign
buttonChange
label: '+/-';
action: [ display label: (display label asNumber negated asString) ];
state: true
This method simply changes the sign of the digit displayed on the screen.
The message precedence is crucial here so its important to follow it.
Parenthesis are evaluated first, therefore (display label asNumber negated
asString) gets the precedence. As all the message following our display object
are unary, the contents are evaluated as usual from left to right. The sequence
is: First we get the contents of the display by sending the label selector to our
display object, second we convert this string into a number, and third negated
it, finally convert it again to a string. After the parenthesis is evaluated the
resulting object is sent back to our display object using the label: selector.
Specalc>>squareRoot
buttonSqr
label: 'Sqr';
action: [ self sqrCheck ];
state: true
This method shows Smalltalks beauty and clarity, almost like pidgin English being, perhaps, (display label asNumber sqrt asString) the only part that
requires an explanation. As in the previous method, between parenthesis
10
we have an object followed by unary messages that are evaluated form left
to right and then sent back to out display calculator.
Lets go ahead and enter this methods, categorising the first two into the
operations protocol and the last one into the private protocol.
You should do:
Finally, the buttonEq is the core of our calculator and will be discussed
once we have finished building the GUI. So go ahead and define the formatOtherButtons method accepting those selectors that are not yet defined.
1.3
Class-side methods
11
add: #button6;
add: #buttonMult ]
height: 26;
newRow: [ :row4 | row4 add: #button7;
add: #button8;
add: #button9;
add: #buttonMinus ]
height: 26;
newRow: [ :row5 | row5 add: #buttonDot;
add: #button0;
add: #buttonEq;
add: #buttonPlus ]
height: 26.
];
yourself.
The following Figure 1.2, will help us clarify how a Speclayout works.
Basically we are setting up our widgets using a kind of matrix approach.
Note, however that theres only one column and the rows are nested inside
of it. So what this method actually does is to return SpecLayout composed [...],
now if you inspect SpecLayout class you will notice that at class-side theres a
method called composed which actually instantiates a SpecLayout object with
12
class you will find the answer, but here is its comment: "I am a model for a
container. My purpose is to hold multiple subwidgets to glue them together.
I should not be used directly"
Therefore our [...] block in the previous paragraph, are exactly subwidgets glued together to form this container!. We are not going to go into details of how Spec works as theres a chapter devoted entirely to it.
Nevertheless, newColumn: and newRow: height: are two of many of the
messages SpecLayout can answer and you should really browse that class to
know the subtle differences among some of them.
1.4
So lets go ahead and execute our calculator. Open a Workspace, type and
do: Specalc new openWithSpec
13
The
debugger
will
complain
with
a:
MessageNotUnderstood
:Specalc>>display
The following Figure 1.3 shows the Workspace with Specalc new openWithSpec and the outcome of the debugger after execution.
14
Overriding #initialExtent
Specalc class>>initialExtent
"(4@4) is the size of the border"
^ (207@187) + (4@4)
Now, execute again Specalc new openWithSpec and you should see the
calculator as shown in Figure 1.1.
1.5
15
Ours calculator core model is really simple and works in three steps:
1. Enter the first number and store it in an instance variable called previousNumber.
2. Enter an operation. This could trigger an immediate result -like in sqrt,
log or sign change- or store the operation in another instance variable
called currentOperation.
3. Enter another number and press the equals (=) button to obtain the
result.
1.6
Conclusion
This chapter illustrates how to use Spec to build applications. A deeper explanation of Spec will be available in a companion chapter.