Rediscovering the UX of the legendary HP-35 Scientific Pocket Calculator

A tutorial on Pharo, test and specification based immersive programming

Sven VC
Concerning Pharo

--

Sometimes, trying to implement a working model of a device is the only way to really understand how it works. It certainly is when the device is old.

I have this fascination with pocket calculators ever since we used them in school. For me, calculators came just before personal computers. I wrote my first ‘computer’ programs on a TI-58.

The mythical calculator company at that time was HP though. Their calculator story started earlier still. And they used RPN (Reverse Polish Notation), this mysterious alternative entry method. How would that work and feel in practice ?

The calculator story is the backdrop for a tutorial on how to use Pharo, a unique immersive live development environment. We’ll be using test and specification based programming, building several simple GUIs.

The appendix describes how to get access to the code described here.

1972

Today we are literally surrounded by digital computers. Of course, there are the obvious ones like your desktop PC or laptop and your smartphone. On top of that, almost everything electronic has some embedded digital computer inside: appliances in your kitchen, children’s toys, anything with an LCD, a battery, a LED or a wire. Your car contains hundreds of microprocessors.

It is hard to imagine how different the world was in 1972. It is the year Pink Floyd released their iconic The Dark Side of the Moon album, when the movies The Godfather and A Clockwork Orange came out, the year the final Apollo mission flew — the last time a human set foot on the moon.

Although the Intel 8008 had just been released, the microprocessor had yet to begin its incredible rise to power. Commercial personal computers for the rest of us, like the Apple II, were still years away (1977). Consumer electronics were mostly analog.

Calculators existed but they only implemented the four basic operations — addition, subtraction, multiplication and division — with limited precision. The few that did more than that were big and expensive desktop machines, more like computers.

Mathematics and engineering did of course exist — people flew to the moon — , so how did the scientist and engineers make their calculations ? They used a slide rule, a kind of mechanical analog computer ! I bet you have never actually seen one, let alone know how to use one.

Photo by Dominic Alves — a slide rule calculating the sine of 71.4°

I remember finding my father’s slide rule only to be utterly amazed by how this could work. I still don’t know, but it has to do in part with properties of logarithms that allow multiplication and division to be transformed to addition and subtraction respectively.

log(x * y) = log(x) + log(y)
log(x / y) = log(x) — log(y)

It can’t have been easy though. Our ancestors must have been way better at doing arithmetic.

The HP-35

Photo by Seth Morabito

It was in this totally different world in 1969 that HP embarked on a project to develop something that did not yet exist: the first scientific pocket calculator. In 1972 they released the HP-35. For the very first time, you could compute logarithmic and trigonometric functions and generally work with extended precision.

All this was so new that they had to describe it as “a high precision portable electronic slide rule”, complete with benchmarks comparing the calculator against a human, while describing the precision as a “two-hundred decade range exceeding the precision to which most physical constants where known”. Furthermore “the calculator automatically places the decimal point for you” — imagine that !

Photo by Hewlett-Packard

On top of being first, the HP-35 was also incredibly well designed. It was simple and elegant yet powerful. The hardware, the case, the keys, the display, each element was designed to be just right. And remember they had to perform several breakthroughs to make the thing possible in the first place.

Have a look at the user manual (available at The Museum of HP CalculatorsHP-35 page). It is very short, less than 50 small pages, even though it spends time convincing the user how useful the calculator is, explains RPN, details accessories, warranty and service terms. At the same time it is very clear and understandable — it has lots of useful, real world examples.

Needless to say, the HP-35 became a success with sales exceeding 300.000 units in just 3 years. In the following years, HP became very successful with its calculator line. The HP-35 was named an IEEE Milestone in 2009 (37 Year Old Calculator Wins Award). It really became a legend.

RPN

The back label of the calculator serving as a quick reference guide

A key characteristic of most HP calculators, including the HP-35, is their use of Reverse Polish Notation (RPN). This is a mathematical notation in which operators follow their operands, in contrast to Polish notation, which puts the operator in the prefix position. RPN is also known as postfix notation and is parenthesis-free as long as operation have a fixed number of arguments.

Normal infix algebraic notation needs parenthesis to control the order of evaluation when it conflicts with operator precedence, RPN does not. Consider the following example:

( 1 + 2 ) * ( 8  5 )

The equivalent RPN is:

1 <enter> 2 + 8 <enter> 5 — *

An operation like addition needs two arguments. The <enter> key is used to separate the two arguments. Technically, the previous number is pushed up on a stack. The addition pops two arguments off the stack, computes the sum and pushes the result back on the stack.

The diagram shows what the stack looks like after the button below it is pressed. HP calculators use a fixed, four element stack labelled x, y, z and t — x being the ‘top’, the position where data goes in or out.

Note how no <enter> is needed after the second number, 2, before the actual operation, +, because the display and the input of the number itself take place on the top of the stack, in x. Also, something called auto enter, or stack lift, happens when starting entry of a new number, 8, after most computations, like + here.

The stack remembers the intermediary results, 3 and 3, for the final operation, *. Because the 4 parenthesis are not needed, less keystrokes are required. An RPN calculator does not need an = key either.

Obviously this needs some getting used to, but once you get the hang of it, RPN is really quite nice. An RPN calculator is also somewhat easier to implement because its underlying model is clearer and the semantics of each key in the different states is a bit simpler to reason about.

Pharo

The Pharo IDE in action (using the optional Dark Theme)

We’re going to build a functional model of the HP-35 to better understand how the calculator works and to be able to get a feel of its user experience (UX), in particular the use of RPN.

The goals is not to replicate its internal algorithms to the last bit (here is an amazing site doing just that) or to exactly mimic its look (here is an online simulator doing that).

We are going to use Pharo, a unique tool that gives you total control over your programming experience. Focused on simplicity and immediate feedback, it is a pure object-oriented programming language and a powerful environment (think IDE and OS rolled into one). It is open source and available for Mac, Windows and Linux.

Pharo Syntax

The conceptual model behind Pharo is simple: everything is an object and everything happens by sending messages to objects. Objects are instances of a class, an object that defines their behaviour — the implementation of methods that handle messages — as well as their layout — the slots for the data they encapsulate. Classes are organised in a single inheritance tree. That’s it.

The syntax is simple and elegant. Let’s look at an example.

'string' reversed

Selecting this piece of code and looking at the result of evaluating it — something you can do everywhere when working in Pharo — will yield

‘gnirts’ 

as result. What happened is that the message reversed was sent to a string, resulting in a new string with the characters reversed. The literal string ‘string’ is called the receiver while reversed is called the message selector (often just message).

Here is one more:

'string' asUppercase

evaluates to

'STRING'

These kinds of messages are called unary messages because they take no arguments.

Sending a message is the only way you can interact with an object

The general form for messages that take arguments uses interpolated keywords ending in colons, like this.

'STRING' copyFrom: 1 to: 3

which results in a new string

'STR'

Note how natural keyword messages read and how they are self documenting with well-chosen names.

The third and last kind of messages are binary messages. There are use for arithmetic operations for example.

1 + 2

Here, the message + is sent to the integer 1 with 2 as argument.

The simpler messages take precedence over the more complex ones, so unary messages are evaluated first, then binary message and finally keyword messages. Parenthesis can be used to control the order of evaluation.

'string' asUppercase copyFrom: -1 + 2 to: 6 — 3

The above evaluates to ‘STR’ while the expression below evaluates to ‘RTS’.

('string' asUppercase copyFrom: -1 + 2 to: 6 — 3) reversed

Note how you can send messages to the result of an expression: copyFrom:to: was sent to the result of asUppercase. This is called chaining.

http://www.youtube.com/watch?v=6p1uvSJ1oAE

To refer to a class, a capitalised name is used. Classes are an important entry point to create objects. Of course, this is done by sending messages to the class object.

String new

Returns a new, empty string, ‘’. Some classes understand many messages, like the class Float that knows about pi.

Multiple statements are separated using a dot, just like sentences. Local variables are declared by writing their lowercase name in between vertical bars.

| array |
array := Array new: 3.
array at: 1 put: true.
array at: 2 put: false.
array

The result of the code above is a new array. Indexing is one-based, like normal humans count.

#(true false nil)

The literal array syntax is #( … ). The first element is the boolean constant true, the second its counterpart false. Uninitialised elements remain nil, the undefined object constant. There are only six reserved keywords, true, false and nil are 3 of them. As you can see, assignment is done using := while the last statement defines the result for the whole program.

Often you’ll be sending multiple messages to the same object, to the same receiver. To make this easier, there is some syntactic sugar called a message cascade using a semicolon. Our previous code could have been written as follows.

(Array new: 3)
at: 1 put: true;
at: 2 put: false;
yourself

The 3 indented messages form a cascade, they are all being sent to the same object, the new array. The last message, yourself, is particulary useful in a cascade: it returns the object it is sent to. This is necessary because the result of the before last message send was not the object itself — the at:put: message returns the value assigned.

Square brackets are used to specify blocks (also known as closures or lambdas), pieces of code to be executed later on.

| adder |
adder := [ :x | x + 1 ].
adder value: 100

The adder local variable is assigned a one argument block. The code inside the block names the variables it accepts and the statements to be executed when it is evaluated. Evaluating a block is done by sending a message, value: with an actual object as argument. The argument gets bound to the variable and the block is executed, resulting in 101.

Blocks are used to express all control structures, from standard conditionals and loops to the exotic application specific ones, using the normal messaging syntax. Here is an actual piece of code from Pharo, the implementation of the method do:separatedBy: on the class SequenceableCollection.

do: elementBlock separatedBy: separatorBlock
“Evaluate the elementBlock for all elements in the receiver,
and evaluate the separatorBlock between.”
1 to: self size do: [ :index |
index = 1 ifFalse: [ separatorBlock value ].
elementBlock value: (self at: index) ]
Selection of a method argument and suggested refactoring in the Nautilus code browser

Methods are entered one by one in a code browser, like the one shown in the screenshot. The first line specifies the method name, the selector, with names for all arguments. Comments are surrounded by double quotes. Inside a method, self refers to the object itself, the receiver. The equivalent of a basic for loop is to:do: which takes a block as its last argument. This block is executed for each index. The conditional, ifFalse:, is written as a message sent to a boolean, true or false, the result of =, comparing two objects for equality and takes a block as its argument.

Pharo Environment

The Pharo runtime and development environment consists of a large set of live objects.

Everything, classes, methods, files, processes, network connections, windows and events are all just objects living in this world, communicating by sending messages. This object world is also persistent, it can be saved to and restored from a file — called an image.

The Pharo IDE is written in itself. You can access the code for the compiler, debugger or editor. It consists of a rich set of tools. These tools work together beautifully and you can write your own tools easily. Working in this IDE is totally immersive: the closest you can get to your code.

Modeling the HP-35

The goal of our project is to model the UX aspects of the HP-35. We are going to build a functioning GUI — both a classic, one window tool inside our environment as well as a web app. It is good design practice to separate the actual GUI from its underlying model. This way, we have a clear separation of concerns, each component becomes simpler, we can test them separately and we can reuse the model for different GUIs.

The UI sends key messages to the model and shows the display string

The HP-35 got its name from the fact that it has 35 keys. The keys are the interface through which the user operates the calculator. It is thus logical to have one message for each key. Apart from the keys, we obviously need the display. We expect the model to tell us what we should show in our GUI. Using the calculator brings it in different states, the model should manage these states and the transitions between them, so that it is of no concern of the GUI code.

The model sends numbers and operations to the core and reads the result as a number

That is already quite a lot of responsibility for one object. We can make another useful distinction. We could make a difference between the pure mathematical part (the core) and the keys, display and state management part. Remember, we want to learn about the semantics of RPN as well.

RPNCalculatorCore

The pure mathematical core of our calculator will deal with numbers, not keys. It will use a fixed, four element RPN stack. It will implement all required basic operations. It will not concern itself with any kind of user interaction.

HP names the elements of its 4 element RPN stack, x, y, z and t. We’ll make those the instance variables (data holding slots) of our object. Creating a subclass is done by sending a message to its super class, in this case Object.

Object subclass: #RPNCalculatorCore
instanceVariableNames: ‘x y z t’
classVariableNames: ‘’
category: ‘HP35-Calculator’

The fact that these 4 elements behave like a stack is something that we will enforce ourselves. Here are some key methods for that.

initialize
super initialize.
self clear
clear
x := y := z := t := 0
enter
t := z.
z := y.
y := x
rollDown
| tmp |
tmp := x.
x := y.
y := z.
z := t.
t := tmp
swap
| tmp |
tmp := x.
x := y.
y := tmp

Upon initialisation, we clear all stack elements to zero. The implementation of initialize overrides an implementation that was inherited from a superclass. To execute the superclass behaviour we send the same message to super before adding our own code.

Enter moves all elements up. Note that x gets duplicated into y and that t drops off. Roll down moves all elements down the stack, with x moving to t. Swap changes x and y for each other. Data enters the core through x. For each element, we’ll add a read accessor.

x
^ x
x: aNumber
x := aNumber.
self ensureXisNoFraction
ensureXisNoFraction
x isInteger ifFalse: [ x := x asFloat ]

The x read accessor uses the explicit return statement, a caret (^). Without such a statement, methods return the receiver (self) — it is as if there is an invisible ^ self at the end.

Pharo has a kind of Number called a Fraction. Divisions between Integers that are not divideable lead to Fractions, like 1/3, not to Floats like 0.3333333333333333 — which is mathematically more correct, especially given the infinite precision of Integers. Because we don’t want this for our calculator, we make sure that numbers that enter our calculator are converted to Float if they are not pure Integers.

We are now ready to start writing unit tests. For this we create a subclass of TestCase, add an instance variable named core and a setup method.

TestCase subclass: #RPNCalculatorCoreTests
instanceVariableNames: ‘core’
classVariableNames: ‘’
category: ‘HP35-Calculator’
setUp
core := RPNCalculatorCore new

Let’s add a test for some basic stack operations.

testStack
core x: 1; enter; x: 2; enter; x: 3; enter; x: 4.
self assert: core x equals: 4.
self assert: core y equals: 3.
self assert: core z equals: 2.
self assert: core t equals: 1.
core rollDown.
self assert: core x equals: 3.
self assert: core y equals: 2.
self assert: core z equals: 1.
self assert: core t equals: 4.
core swap.
self assert: core x equals: 2.
self assert: core y equals: 3

We enter data into the core and do some operations, validating the contents of the stack elements as we go by making explicit assertions.

Writing tests before implementing the necessary functionality, test driven development, or afterwards is a question of development style. The important thing is to write good tests. Pharo supports both styles equally well.

Here are the tests for a binary and a unary operation, add and negate respectively.

testAddition
core x: 1; enter; x: 2; add.
self assert: core x equals: 3
testNegate
core x: 3; negate.
self assert: core x equals: -3

To implement those, we need a way to drop down the stack after consuming x and y.

add
x := x + y.
self dropY
dropY
y := z.
z := t.
t := 0
negate
x := x negated
Debugging testAddition and looking at the core’s RPN stack right before dropY is executed

We named our helper method dropY, because it essentially drops down the stack limited to y. Note how a zero enters the stack at the top t element. The x element was already overwritten by the result of the computation.

All 16 other operations are implemented along the same principles. To prevent the introduction of Fractions, divide and reciprocal also call ensureXIsNoFraction. The trigonometric functions use degreesToRadians and radiansToDegrees so that they conform to what the HP-35 does.

https://www.youtube.com/watch?v=PHsXyQKwG-0

Of course, code is not written in a linear fashion — the way we are describing it here. Instead, coding and testing are interwoven. The screencast above tries to give an impression of the interaction between a couple of tools of the Pharo IDE — the code browser, debugger, inspector and explorer.

HP35CalculatorModel

We are now ready to implement the full HP-35 calculator model. It will reuse the mathematical calculator core and add a single memory register. It will also add state information regarding number input and operations. The model will implement one method for each of the 35 keys.

Object subclass: #HP35CalculatorModel
instanceVariableNames: ‘core memory input inputState
arcMode autoEnter error’
classVariableNames: ‘’
category: ‘HP35-Calculator’

Here is the initialisation code, as well as the code for two keys, CLEAR and CLx. The ground state has all register set to zero, arc and auto enter mode off and no number input in progress.

initialize
super initialize.
core := RPNCalculatorCore new.
self clear
clear
core clear.
memory := 0.
self clx
clx
core clearX.
error := nil.
self resetInput
resetInput
input := String new.
inputState := #new.
arcMode := false.
autoEnter := false

The hashtag like in #new is the syntax for a literal Symbol, a unique, constant String.

Many keys are implemented by delegating directly to the core, which will do the actual computation or stack manipulation.

add
core add.
self resetInputEnableAutoEnter
resetInputEnableAutoEnter
self resetInput.
autoEnter := true

Auto enter mode is enabled after most computation operations. The effect is that starting number input, inputing the constant ∏ or recalling the value stored in the memory register (RCL) will execute enter on the stack. Only CLx, storing into the memory register (STO) and ENTER itself will not enable auto enter mode.

ARC is a key to change a subsequent trigonometric operation from the regular to the inverse function.

arc
arcMode := true
cos
self executeProtected: [
arcMode
ifTrue: [ core arcCos ]
ifFalse: [ core cos ].
self resetInputEnableAutoEnter ]
executeProtected: block
block
on: ArithmeticError
do: [ :exception | error := exception ]
hasError
^ error notNil

Some mathematical operations might lead to an error. The core will throw an exception of type ArithmeticError when that happens. With executeProtected: we catch this error and store it. This state will be used when deciding what to show in the display.

A major responsibility of our model is managing number input. This is inherently a process involving a complex state. Two instance variables, input and inputState collaborate to track this state. The value of inputState is a Symbol from the list #(new integer fraction exponent) — inputState values evolve from left to right. The value of input is a String, more or less a concatenation of data entered.

The model will make sure that the input number is reflected in the x register of the core.

Apart from the digit keys, the other keys involved in number input are the dot (decimal) key, the EE (exponent entry) key and the negate (+/-) key. What happens when these keys are pressed depends on where we are in the state of the number input.

Some of the following methods will be more complex. Larger methods containing multiple conditionals are usually an indication of lower code quality — in this case it is preferrable to the alternative, extra objects to manage the state.

seven
self digit: 7
digit: digit
inputState = #new
ifTrue: [
self autoEnter.
input := digit asCharacterDigit asString.
inputState := #integer.
^ self updateXEnableAutoEnter ].
(inputState = #integer) | (inputState = #fraction)
ifTrue: [
input := input , digit asCharacterDigit asString ].
inputState = #exponent
ifTrue: [
(input includes: $e)
ifFalse: [ input := input , ‘e’ ].
input := input , digit asCharacterDigit asString ].
self updateX ]
updateX
core x: input asNumber
updateXEnableAutoEnter
self updateX.
autoEnter := true
autoEnter
autoEnter ifTrue: [ self enter ]

Each named digit key method, like seven, is delegated to a single digit: method. What happens when a digit is entered depends on the input state.

When starting a new number, auto enter is done on the stack, the input string is initialised with the digit converted to a Character/String and finally the x register gets updated. Auto enter is enabled. The input state moves from new to integer.

In either integer or fraction input state, digits are simply appended.

In exponent input state, the digit is also appended, but only after making sure an E indicator is not yet present in the input string. The literal syntax for Character objects is a dollar sign followed by the character itself. The binary comma (,) selector selector does concatenation.

Transitioning from the the integer to fraction state happens when the dot key is pressed.

dot
inputState = #new
ifTrue: [
self autoEnter.
input := ‘0.’.
inputState := #fraction.
^ self updateXEnableAutoEnter ].
inputState = #integer
ifTrue: [
input := input , ‘.’.
inputState := #fraction.
^ self updateX ]

Starting a new number with a dot is a special case. The leading zero is necessary to accomodate the Pharo number parser in asNumber (‘0.’ is OK, ‘.’ alone is not). Next is the handling of the EE (exponent entry) key.

ee
inputState = #new
ifTrue: [
self autoEnter.
input := ‘1'.
inputState := #exponent.
^ self updateXEnableAutoEnter ].
inputState = #integer
ifTrue: [
inputState := #exponent.
^ self updateX ].
inputState = #fraction
ifTrue: [
(input endsWith: ‘.’)
ifTrue: [ input := input , ‘0' ].
inputState := #exponent.
^ self updateX ]

Starting a new number with EE will automatically insert a 1, a conveniece feature. Going to the exponent input state does not yet add an E to the input — ‘1E’ is not parseable as a number — this happens in the exponent case in digit: later on. When going from fraction to exponent input state we again have to accomodate the Pharo number parser (‘2.0E10’ is OK, ‘2.E10’ not).

negate
inputState = #new
ifTrue: [ core negate ]
ifFalse: [ self negateInput ]
negateInput
inputState = #exponent
ifTrue: [
(input includes: $e)
ifTrue: [ self negateExponent ] ]
ifFalse: [
input first = $-
ifTrue: [ input := input allButFirst ]
ifFalse: [ input := ‘-’ , input ] ].
self updateX
negateExponent
| exponent parts mantisse |
parts := $e split: input.
mantisse := parts first.
exponent := parts second.
exponent first = $-
ifTrue: [ exponent := exponent allButFirst ]
ifFalse: [ exponent := ‘-’ , exponent ].
input := mantisse , ‘e’ , exponent

The negate (+/-) key has several duties. When there is no number entry in progress, the number in x, the top of the stack, is just negated mathematically. When number entry is in progress, we also have to negate the input string, which might already be negative and then becomes positive.

Furthermore, in integer or fraction input mode the number itself is negated, while in exponent mode, only the exponent changes sign. In all cases, updateX keeps the input string and x in sync.

The final responsibility of the model is to provide the string to show in the display, reflecting the internal state of the calculator. We are using UPPERCASE output for a retro styling effect.

displayString
^ self hasError
ifTrue: [ error class name asUppercase ]
ifFalse: [
inputState = #new
ifTrue: [
core x abs >= 1e10
ifTrue: [ core x: core x asFloat ].
core x printString asUppercase ]
ifFalse: [ self inputString ] ]
inputString
| inputString |
inputString := input asUppercase.
(inputState = #exponent and: [ (input includes: $e) not ])
ifTrue: [ inputString := inputString , ‘E’ ].
^ inputString

If there is an error, we display the class name of the exception, like ZERODIVIDE or DOMAINERROR. If there is no error, what we do depends on whether we are entry of a new number is in progress or not.

If there is no new number input in progress, we convert the x value to a string. Remember that native Pharo Integer and Fraction arithmetic is of infinite precision. So we force conversion of any number that is too large to a Float and thus scientific notation, so that it fits in a small display.

If there is new number input in progress, the input string is shown. This is done because under certain conditions, the input string and the number value in x are not identical — extra zeros before or after a number for example. Note that if we are in exponent input mode, but no digits were typed yet, we still show an E to inform the user of the current state.

Testing

The main idea of our project is to reconstruct the UX of the original HP-35. Problem is that is very hard to get your hands on a working model. And if you would find one, it would be an expensive collector’s item. However, scanned copies of the original user manual can be found. The manual contains numerous examples, with expected display output for each step.

A page from the original HP-35 operating manual

We can convert these elegant usage diagrams directly into functional tests. The second example on the page above translates to the following abstract test.

testHypotenuseRightTriangle
“page 8"

self runSequence: #(
three 3 enter 3
multiply 9
four 4 enter 4
multiply 16
add 25
sqrt 5)

This is an abstract test because we have not yet defined runSequence: something we’ll do in a subclass. The argument to this method is an array that should be interpreted as pairs, the name of a key to be pressed and the value of what should be visible on the display.

In the previous test, the data was supplied using a literal array. Literal arrays are cool, but they cannot contain computations — code. For that we need a dynamic array.

testCircleArea
“page 9"

self runSequence: {
#three. 3. #enter. 3.
#multiply. 9.
#pi. Float pi.
#multiply. 3 * 3 * Float pi }

Such a dynamic array is enclosed in curly braces while elements are separated by dots — each element is an expression.

The test suite contains all 41 examples from the original manual plus some related to error handling and then some regression tests that came up during development. Such a set of functional tests is an extremely important tool to produce high quality code.

Here is the concrete test subclass for the model.

setUp
calculator := HP35CalculatorModel new
runSequence: script
script pairsDo: [ :key :result |
calculator perform: key.
self
assert: calculator displayString
equals: result asString asUppercase ]

The script is run by iterating over it, executing the block for each pair. The perform: message is used to send the selector key to the calculator. Then the display is checked for the expected result.

HP35CalculatorUIModel

Now that we have a working and fully tested model, we can build UIs on top of it. Pharo has a toolkit called Spec that can be used to quickly build a UI — a major goal is to be able to specify your UI. Another goal of Spec is to improve reusability among UI components, something we don’t need here.

Our ComposableModel subclass will have three instance variables: one holding the display, one holding our calculator model and a dictionary holding the 35 buttons.

The word ‘model’ is used in two contexts here: as our application’s domain model and as a UI element in Spec. ComposableModel is the model of the user interface, defining how it behaves. The domain model does the calculations.

ComposableModel subclass: #HP35CalculatorUIModel
instanceVariableNames: ‘display buttons calculator’
classVariableNames: ‘’
category: ‘HP35-Calculator’
initialize
super initialize.
buttons := Dictionary new.
calculator := HP35CalculatorModel new
initializeWidgets
display := self newTextInput
enabled: false;
text: ‘0';
yourself
“the buttons are created lazy”

For each UI element, Spec requires an accessor. Here is the one for the display. We will also need a little helper method to update the display.

display
^ display
updateDisplay
display text: calculator displayString

We use a disabled text input field as our display. The work of deciding what to show in the display is delegated to our calculator model.

Now, writing code for 35 buttons can be a lot of work, so it makes sense to minimise the amount of typing. One good way to do that is to use declarative specifications. What is the information that we need per key ?

  • an identifier, let’s use the message selectors of our model
  • the label to display on the button key
  • for some keys, an optional keyboard accelerator shortcut

How are the keys layed out ? In 8 rows. We can write the following on the class side of our class. Each row specification is an Array containing Associations — key/value pairs where the key is the identifier and the value a two element Array containing the label and the optional shortcut.

The word ‘spec(ification)’ is used in multiple contexts, which might be confusing — be careful.

buttonsSpecRows
^ {
self rowSpec1.
self rowSpec2.
self rowSpec3.
self rowSpec4.
self rowSpec5.
self rowSpec6.
self rowSpec7.
self rowSpec8
}
rowSpec1
^ {
#power -> { ‘x^y’. nil }.
#log -> { ‘LOG’. nil }.
#ln -> { ‘LN’. nil }.
#exp -> { ‘e^x’. nil }.
#clear -> { ‘CLEAR’. nil }
}
rowSpec2
^ {
#sqrt -> { ‘SQRT’. nil }.
#arc -> { ‘ARC’. nil }.
#sin -> { ‘SIN’. nil }.
#cos -> { ‘COS’. nil }.
#tan -> { ‘TAN’. nil }
}
rowSpec3
^ {
#reciprocal -> { ‘1/x’. nil }.
#swap -> { ‘SWAP’. nil }.
#rollDown -> { ‘ROLL’. nil }.
#sto -> { ‘STO’. nil }.
#rcl -> { ‘RCL’. nil }
}
rowSpec4
^ {
#enter -> { ‘ENTER’. $= asShortcut }.
#negate -> { ‘+/-’. nil }.
#ee -> { ‘EE’. $e asShortcut }.
#clx -> { ‘CLx’. $c asShortcut }
}
rowSpec5
^ {
#subtract -> { ‘-’. $- asShortcut }.
#seven -> { ‘7'. $7 asShortcut }.
#eight -> { ‘8'. $8 asShortcut }.
#nine -> { ‘9'. $9 asShortcut }
}
rowSpec6
^ {
#add -> { ‘+’. $+ asShortcut }.
#four -> { ‘4'. $4 asShortcut }.
#five -> { ‘5'. $5 asShortcut }.
#six -> { ‘6'. $6 asShortcut }
}
rowSpec7
^ {
#multiply -> { ‘*’. $* asShortcut }.
#one -> { ‘1'. $1 asShortcut }.
#two -> { ‘2'. $2 asShortcut }.
#three -> { ‘3'. $3 asShortcut }
}
rowSpec8
^ {
#divide -> { ‘/’. $/ asShortcut }.
#zero -> { ‘0'. $0 asShortcut }.
#dot -> { ‘.’. $. asShortcut }.
#pi -> { 960 asCharacter asString. $p asShortcut }
}
buttonsSpec
^ self buttonsSpecRows flattened asDictionary

960 is the decimal code point for the Unicode character ∏. The method buttonsSpecRows returns an Array of rows, maintaining the row structure, while buttonsSpec flattens everything in a Dictionary.

Given this high level description, we can use it to write our UI specification.

defaultSpec
<spec: #default>
^ SpecLayout composed
newColumn: [ :col |
col newRow: [ :row | row add: #display ].
self buttonsSpecRows do: [ :rowSpec |
col newRow: [ :row |
rowSpec do: [ :each | row add: each key ] ] ] ];
yourself

This UI specification will be used by the Spec framework to build the UI. Everything is put in one vertical column. The display comes in a row all by itself. Then we add a row for each row in the high level description and in each row, we add each calculator key (held as the key of the Association). The UI specification concerns itself with the composition and layout of UI models.

Note how abstract the Spec spec is (pun intended): it refers to each element using a symbolic identifier. How does that work ? At runtime Spec will use these identifiers as selectors and send them as message to our UI model. Remember how we already implemented the display method ?

Now we have to write 35 methods, one for each button. Not only that, but they’ll all be the same ! Here is the general implementation of a button, given its id (we’re back on the instance side now).

makeNewButton: id
| button buttonSpec |
button := self newButton.
buttonSpec := self class buttonsSpec at: id.
button label: buttonSpec first.
buttonSpec second
ifNotNil: [ :shortcut | button shortcut: shortcut ].
button action: [ self performKey: id ].
^ button
performKey: id
calculator perform: id.
self updateDisplay
button: id
^ buttons at: id ifAbsentPut: [ self makeNewButton: id ]

In makeNewButton: we are consulting our class side description to find our label and shortcut. The button’s action is to send a message with that id to the calculator model using perform:, after which we update the display. Given these helper methods, each button method is now reduced to its bare minumum.

add
^ self button: #add

The problem of writing those 35 silly methods is still not solved though. Can’t we write them automatically ? Yes we can !

generateButtonAccessors
“self generateButtonAccessors”

self buttonsSpec keysDo: [ :id |
| source |
source := String streamContents: [ :out |
out
<< id; cr;
tab; << ‘^ self newButton: ‘; print: id ].
self compile: source classified: ‘buttons’ ]

The class method above generates and compiles source code for each button accessor (similar to code for the add method shown above)— problem solved.

https://www.youtube.com/watch?v=zUaqgbcXckM

Our GUI is relatively simple: it delegates to the calculator model, which we tested before. But QA will require testing of the whole widget. We can do this manually, but wouldn’t it be great if we could automate testing the GUI ? Turns out we can use our functional test suite that we wrote before because Spec models are naturally scriptable.

Here is another concrete test subclass, this time targeting the Spec UI model.

setUp
calculator := HP35CalculatorUIModel new.
calculator openWithSpec
tearDown
calculator window close
runSequence: script
script pairsDo: [ :key :result |
(calculator perform: key) performAction.
self
assert: calculator display text
equals: result asString asUppercase ]

This code literally runs the tests sequences over the calculator GUI’s key buttons and checks the result in the display input field — pretty cool, no ?

HP35CalculatorWebComponent

So, we have this nice calculator model separate from its Spec UI. And we have a high-level description of the UI’s layout that was already pretty useful. We can easily reuse this work to build a web app version of our HP-35 calculator.

Seaside is a very high level web application framework for Pharo. It abstracts away the HTTP request/response cycle and uses continuations to allow proper component based architectures. It allows you to generate HTML using a DSL in Pharo itself. Let’s get started — code talks.

WAComponent subclass: #HP35CalculatorWebComponent
instanceVariableNames: ‘calculator’
classVariableNames: ‘’
category: ‘HP35-Seaside-Calculator’
initialize
super initialize.
calculator := HP35CalculatorModel new

The main method that generates (part of) the web page for a component is called renderContentOn: — first there is a heading (H1), followed by a form.

renderContentOn: html
html heading: ‘HP-35'.
html form: [
html div
class: #calculator;
with: [
self renderDisplayOn: html.
self renderKeysOn: html ] ]
renderDisplayOn: html
html textInput
id: #display;
readonly: true;
with: calculator displayString

Inside the main div in the form we put a read-only text input field (input of type text) for the display and configure it to show the display string of the calculator model. For the buttons and their layout in rows, we’ll be using the high level description from before.

renderKeysOn: html
HP35CalculatorUIModel buttonsSpecRows do: [ :row |
row do: [ :each |
html div
class: #key;
style: (‘width:{1}%’ format: { 100 / row size });
with: [ self renderKey: each on: html ] ] ]
renderKey: each on: html
html button
id: each key;
callback: [ calculator perform: each key ];
with: each value first

The callback (action) block of each button uses perform: to send the corresponding message to the calculator model. At first sight it might seem that we are ignoring or at least are not really doing anything with each row — like put them in a div. Actually, the grid appears as a consequence of the width set on each button div. Consider the CSS below.

style
^ ‘div.calculator { width: 512px }
input, button { font-size: x-large }
input { width: 98%; float: left; }
button { width: 96% }
div.key { float: left; margin-top: 4px }’

The buttons on each row total 100% in width, which in combination with floating them left results in rows — and in a nice grid. The following two methods round out the implementation of our web component.

updateRoot: anHtmlRoot
super updateRoot: anHtmlRoot.
anHtmlRoot title: ‘HP-35'
states
^ Array with: calculator

The states method is part of the Seaside machinery that implements backtracking when the browser’s back button is used. In a normal web application, going back one or more steps will completely confuse your web app. Not so with Seaside: you can use the back button as a kind of undo, with the whole state of the calculator going back in time !

QA comes calling again: please organise some automatic functional tests for the calculator’s web app GUI. It is a bit more involved, but we can do this. We need an HTTP client that we can drive programmatically. We also need to understand HTML, at least enough to figure out how to click each button and where to find the display’s contents. Since the HTML output of Seaside is actually XHTML, an XML parser will do.

setUp
adaptor := ZnZincServerAdaptor port: 8888.
adaptor start.
client := ZnClient new.
client contentReader: [ :entity |
XMLDOMParser parse: entity contents ].
client url: ‘http://localhost:8888/hp-35'
tearDown
client close.
adaptor stop; unregister

The adaptor is the server. The client parses the contents that it reads into an XML DOM tree. We can now implement the web app version of runSequence:

runSequence: script
page := client get.
script pairsDo: [ :key :result |
self performKey: key.
self
assert: self display
equals: result asString asUppercase ]
performKey: id
| form button |
form := page allNodesDetect: [ :each | each name = #form ].
button := page allElementsDetect: [ :each |
(each attributeAt: #id ifAbsent: [ nil ]) = id ].
client url: (form attributeAt: #action).
client formAt: (button attributeAt: #name) put: ‘’.
page := client post
display
| input |
input := page allElementsDetect: [ :each |
(each attributeAt: #id ifAbsent: [ nil ]) = #display ].
^ input attributeAt: #value

Before we start, we read the initial page using a GET. Executing the script is basically the same as before, using two helper methods.

The first helper methods, performKey:, mimics how the browser handles a click on a button in a form. The form will be submitted with the button’s name. Here we select the form and button out of the DOM tree. We set the new URL of the client to the action URL of the form. Then we prepare the payload, an application/x-www-form-urlencoded entity, with the button’s name as key. Finally we submit by doing a POST.

The second helper method, display, selects the display input element out of the DOM tree and returns its value.

https://www.youtube.com/watch?v=M3Euu1XBo1U

Modern web applications try to avoid full page refreshes because, even though current browsers and networks are quite fast, these are often perceived as slow. The solution is to use JavaScript code to manipulate the page ‘in place’ for an almost native feeling UI. Although explaining the details would lead too far, it requires only one change to implement a JQuery based AJAX calculator.

renderKey: each on: html
html button
id: each key;
bePush;
onClick:
(html jQuery ajax
script: [ :s |
calculator perform: each key.
s << ((s jQuery id: #display)
value: calculator displayString) ]);
with: each value first

We subclass our web component and overwrite renderKey:on: and replace the callback by an onClick JavaScript handler that gets generated by a DSL making use of jQuery. There is still interaction with the server, but only the contents of the display gets updated, not the whole page.

This concludes the tutorial. I hope you found it interesting. There is much more to learn and discover about Pharo. I can promise you that doing so will change your perspective on software.

I thank Hein Saris, Stéphane Ducasse and Johan Fabry for proofreading early drafts of this article and for providing feedback — Sven Van Caekenberghe, June 2014.

Appendix: Getting the code

There are several ways to get access to the source code of the HP-35 example described here. The code was written against Pharo 3. The Web UI code requires Seaside 3 and XML Support.

Prebuilt Image

There is a continuous integration (CI) job on the Pharo Contribution CI server that builds several image artefacts with all the code loaded. The relevant URL is https://ci.inria.fr/pharo-contribution/job/HP35/. Select one of the successful stable builds and download the hp35.zip archive. You will need a Pharo VM to run this image.

With the image open, type Shift-Enter followed by HP35. You will see a number of completions where you can select one of the main classes — a browser will open and you’re off.

Configuration Browser

If you first download Pharo 3, you can use a graphical tool to quickly install the base code, the Configuration Browser. Open this tool via World Menu > Tools > Configuration Browser. Find the HP35 project, select it and click the ‘Install Stable Version’ button.

To load the Web-UI part, execute the following script — this script can also be found in the class comment of the class ConfigurationOfHP35.

ConfigurationOfHP35 project stableVersion load: ‘Web-UI’

This second step will take several minutes because it loads a lot of code — you can avoid that by downloading a prebuilt image.

Gofer and Metacello

There is also a simple Pharo script that you can execute to load the base code.

Gofer it
smalltalkhubUser: ‘SvenVanCaekenberghe’ project: ‘HP35';
configuration;
loadStable

The second step, loading the Web-UI part, can be done like above.

Monticello

If you know what you are doing, you can download the code directly from its repository, http://www.smalltalkhub.com/#!/~SvenVanCaekenberghe/HP35/. There are two packages:

The first one contains the base code with the Spec UI, the second one contains the Web UI — it thus requires Seaside 3 and XML Support, as mentioned before.

--

--

Sven VC
Concerning Pharo

loves high-level, dynamic and interactive object-oriented systems