|This tutorial is meant to be used in a Morphic Project. To begin a
new Morphic Project, simply hold the mouse button down on the main Squeak
window background (away from any other windows it contains) to pop up the
screen menu, move your mouse pointer down to "open...", and release the
mouse button to select it; this will pop up another menu - click on "morphic project". A small orange window titled "Unnamed" appears. Click
once in the middle of it to zoom into the new, blank Morphic Project (known
as a Morphic "World") - it will fill the entire main Squeak window
with a light gray background. (If you are not sure where you are, just
pop up the screen menu - if its title is "World", you are already in a
To learn how to exit this new project or save your work as you go along, see the explanation at the end of this tutorial.
Let's break the ice by writing a really simple program. To begin, we
need to open a system browser. From the screen menu, select "open...",
then click on "browser". A green browser appears.
This is a little large, so we want to resize the window. Move the mouse to the lower-right corner. The cursor will change to indicate that you can resize the window. Click and drag the mouse to resize the browser.
In an object oriented system, the logical thing to do is to build a really simple object. It will be an instance of a new class we will create. But first, we need to make a place to put our new class. To do this, we will add a new class category (also known as a system category). Class categories are found in the top left pane of the browser. (In recent Smalltalks, it may be the second pane from the left...) Put your cursor in that pane, click the right mouse button, and select "add item..." from the pop-up window. [CS 334: If clicking the right mouse button does not work, click the middle mouse button whenever the right-button is mentioned. If that doesn't work, Ctrl-Click...] A small prompter window pops up to let you type in a new class category. Type in "My Stuff" (without the double quotes) and click on Accept, or just hit the Enter key. Your new class category will be added to the list (either where you have position the cursor, or end of this whole list of class categories). Right after you created your new class category, the bottom pane of the browser (the code pane) changes to:
This is a template that lets you define a new class. First we will edit "NameOfSubClass" to be the name we want to give our new class. Double-click on "NameOfSubClass" to select it and type "BankAccount". (Like many text editors, if you select something and start typing, it replaces the current selection with what you type.) Next I will place the cursor between the two single quotes on the line for instanceVariableNames. Type "balance" there, between the quotes. Our goal here is to create a kind of object called a BankAccount, that is, a class of objects called BankAccount. I want those objects to have one piece of state, one instance variable, which I'll call "balance". I don't want it to have any class variable names, so I will not add anything inside the single quotes on the next line. Now you should see:Object subclass: #NameOfSubClass instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'My Stuff'
At this point, you've created the description of your new class. It is a subclass of Object. Object is the grandfather of all objects in the system. The thing before "subclass:" is the superclass, and the thing after is the new class I'm creating. It has a single instance variable called "balance". It doesn't have any class variables or pool dictionaries, so don't worry about what those are right now. Your new class will be in the category "My Stuff".Object subclass: #BankAccount instanceVariableNames: 'balance' classVariableNames: '' poolDictionaries: '' category: 'My Stuff'
Notice that the code pane has a red line running around its inner boundary -- this means that your class definition still needs to be saved. To save our work. we need to accept our new class definition: put your cursor in the code pane and hold down the right mouse button [Option-click on Macintosh computers - Macintosh key sequences will be shown this way in the rest of this tutorial] and select "accept" from the code pane menu - or just type Alt-s [Command-s]. Now you should see "BankAccount" appear highlighted in the second pane (the class names pane).
Now we have this new kind of object, but it doesn't actually do anything yet. So we're going to start with our empty class definition and add to it.
Let's open up another kind of window -- called a workspace. It
is just a scratch text pane where you can type snippets of code and try
them out. So again, I go to the screen menu, press the left mours button, select "open...", and click
on "workspace". I'm going to resize it, just as I did with the browser,
then move it under the browser and over on the right side (you can drag
a whole window around by holding the mouse button down on the window's
title bar - away from the title, and the X and O - and dragging the outline
to its new position).
It's okay to overlap the browser and the workspace -- just click on a window to bring it to the front.
Select and copy this text:
b _ BankAccount new.
Put the cursor in the workspace window and paste the text into it by typing Alt-v [Command-v]. Remember, all of the editing commands also appear on the right button [Option-click] menu for the workspace .
Notice that the underbar or underscore character in the first line you just copied over has changed to a left-pointing arrow in the workspace window. This is Squeak's normal assignment operator and you type it in as an underbar (shift-minus). As an alternative, you can also use colon-equals ":=" for the assignment operator (which may be familiar to you from other languages like Pascal).
Select the first line, and choose "printIt" from the right button menu (the menu is also at the top of the scroll bar). This sends the message "new" to the BankAccount class, which responds by creating a new instance of itself (a new BankAccount object), which is then assigned to the variable "b" -- then the highlighted text "a BankAccount" is printed right after the line you selected - telling you what was returned by the expression (just hit the backspace key to delete the extra text).
We've defined a class BankAccount, which doesn't do anything yet, and we've just made an instance of it. We'd like to look at our instance. There's a message "inspect" that can be sent to any object. Select the second line, "b inspect" and choose "doIt" from the workspace menu. An inspect window titled "BankAccount" will pop up. In its top left pane, click on the instance variable "balance" - this will show balance's value in the top right pane -- we see that it is "nil". (Leave this inspect window open and visible - we will refer to it later.)
Note, "nil" is actually an object, just like everything else in Squeak. It's an instance of a class called UndefinedObject. We use nil to refer to any variable that hasn't yet been assigned (initialized to) another value. So, by default, every variable in Smalltalk is always initialized -- unlike C, where, depending on what the last random garbage on the stack was, your variable could have any value in it. Smalltalk, and Squeak is a kind of Smalltalk, always initializes its variables, so you can count on your variables having a repeatable value. And if you haven't done anything else, it will be nil.
We want to make this object do something -- so let's make it return the balance. So I'll start by just sending the message "balance" to "b". I actually know it's not going to work, but let's see what happens. Copy this over to your workspace:
Select this, and choose "printIt". A menu pops up telling you that "balance" in an unknown selector and offering some possible choices to select instead. Nobody's ever defined the selector "balance" in this Squeak before. So I'll say, "yes, I really did mean balance" by clicking on "balance" in the list the menu gave me.
Then you see a small red walkback window appear. It tells you that that something didn't understand the message "balance", and displays some of the stack showing the order of message sends that led up that point. doIt is here, and that is where the code actually is. The top line, here, says BankAccount, and in parentheses, it says "Object", because BankAccount is a subclass of Object. A BankAccount doesn't understand the message "balance". Since we know what the problem is, we need not go any further here -- just click on the X on the left side of the title bar to close the window.
Come over to the browser and go to the third pane from the left -- this is the message category pane. Click on the "no messages" category to select it. Now you see a new template appear in the code pane:
When that looks right (except "balance" will not be bold yet), right-click and choose "accept" from the code pane menu. This little window pops up saying, 'Please type your initials'. What does that have to do with accepting a method? Well, the reason it wants to know your initials is because it's going to tag all of the changes you make with your initials. Just type your initials and hit the Enter key or click the "Accept" button (now you should see "balance" change to "balance"). (Once someone has entered their initials in a Squeak image and saved the image, it does not ask again.) Now that we have added a new method, notice that "no messages" in the message category pane has changed to "as yet unclassified".balance "Return the balance" ^ balance
Let's go back to the workspace and send the message. Select "b balance" again and choose "printIt". It returns an uninteresting value, nil. We knew, from looking at the inspector, that we'd get that. We'd like to get a more interesting value into there. Let's add an initialize method. Back in the Browser, select everything in the bottom pane (just double-click in the code pane well below any text it may contain) and replace it with:
I'm getting tired of selecting "accept" from the menu, so I'll do Alt-s [Command-s]. So any modified text that's in the bottom pane, becomes a new method when you accept it. It doesn't matter if 'balance' was the old method. It looks at the keyword at the top of the method text to decide what method it is. A little thing to remember is that a category must be selected in third pane of the browser when you accept, or it won't know that your text is a method.initialize balance _ 0.
Now we have two methods here. I'm going to actually send the message "initialize". By the way, the "initialize" message is an idiom. Typically you create an object, or define a class, and one of the first things you do is define the "initialize" method. So the instance variables that you've defined get some meaningful initial value. In our case, the initialize method sets the balance to zero. Now type this in the workspace, select it, and choose "doIt".
Notice that the balance in the BankAccount inspector we opened earlier changes from nil to 0. Or, we can also select "b initialize" and type Alt-p [Command-p] for "Print It" -- which tells us once again that the balance is now zero.
One of the nice things about the Morphic world is that a window can be updating itself all the time. Things like inspectors really do try to keep themselves up to date. Even though this wasn't the active window, because it wasn't selected, it did update the balance, and we can see that the balance is zero.
We're actually almost done making a working object here. Select all the text in the bottom pane of the browser again and replace it with:
This one is the "deposit" method. And it's only one line long. Obviously, deposit will just increment the balance by the amount of the deposit. Now "accept" that. In the workspace, type:deposit: amount balance _ balance + amount.
b deposit: 10.
Select that and do Alt-d [Command-d] to doIt -- notice that the balance immediately incremented itself over here in the inspector to show 10. Now let's paste
b deposit: 100 factorial.
into the workspace and select it and do Alt-d [Command-d]. That might be more money than there is in the world, though. (I love seeing the 10 from our previous balance added at the end of that number.)
Now we've built a bank account. It's got three methods: an initialize, a way of returning the balance, and a way of depositing.
I guess I should back up a little and say something about how objects in Squeak define their own interfaces to the world. One of the things that they won't let you do is look at the values in their instance variables without sending them a message. Unlike a record or a structure or something in C, before I could find out the value of this instance variable, I actually had to implement the message, "balance". That is called 'information hiding'. It means that you can change your mind about the representation of a BankAccount rather easily, because you know that the only way people can be depending on it is through the message interface.
For example, I could have a different kind of BankAccount, which was something that depended on the current value of a basket of stocks or something. I could still ask for the balance of that Bank Account, by sending it the message "balance". But instead of returning the value of an instance variable, it might multiply the number of shares of stock I have by their current prices. The nice thing about that is, if I made this kind of BankAccount, everybody who is using the old kind of BankAccount would not have to change their code to accommodate it. Since they were only sending the message, balance, not knowing anything about its internal structure, they would always get the right answer.
Back to Syntax for a moment. Some of the lines end with period. Period, in Smalltalk, is a lot like semi-colon in C. It's the statement separator. In this case, since there's only a single statement, you don't actually need it. I usually leave the period there in case I later add another line to the method.
One more thing about this browser window. Remember that if there's unaccepted text in it, it will show this tiny little border of red. That means that I have edited this method, but I haven't yet accepted it. It's possible that I don't really want to accept it. If I just evaluated a "printIt" in it, I don't really want to keep the changed text -- I want the original thing. So there's a command called "cancel", right under Accept, and there's a shortcut for it, which is Alt-l (lowercase L, not a one) [Command-l]. That will undo all the edits I made and revert to the previously saved version of this method.
Now let's add a "withdraw" method -- withdraw is just like deposit, except that it checks for a negative balance.
The next thing I want to do is to show how you can evolve an object, even if you already have an instance of it. This is a hard thing to do in a language like Java or C. I am going to change this object on the fly, to maintain a history of the BankAccount balance.withdraw: amount amount > balance ifTrue: [ ^ self inform: 'sorry, not enough funds']. balance _ balance - amount.
In the browser, click on the "instance" button below the class names pane (the second pane). You'll see the class definition of BankAccount in the bottom pane again. I want to add a new instance variable, called "history". Click right after "balance" and type in "history" after that.
Again, you can see that it's not accepted yet because it's still got the red around it. Do Alt-s [Command-s] to accept the new class definition. The existing instance, b, already has a new instance variable that's been added and initialized to nil. But the inspector isn't quite smart enough to figure that out. So, close that inspector, select a "b" anywhere in the workspace window, and do Alt-i [Command-i] to get a new inspector. Now we can see our new instance variable, history. All existing instances are changed when you accept a change to the class's shape. (You can have as many different inspectors open as you wish - we only closed the old inspector here because it was showing an obsolete view of our instance.)Object subclass: #BankAccount instanceVariableNames: 'balance history ' classVariableNames: '' poolDictionaries: '' category: 'My Stuff'
By the way, you might wonder what kind of a variable "b" is. Workspaces have a special dictionary of variable bindings associated with them. So, I didn't have to declare "b" in any way. We also have global variables. By convention, global variables start with capital letters, but locally scoped variables, in methods and workspaces, start with a lower case letter.
A lot of the style of programming in this system is "late-binding programming". You can reformulate things that you've already created. And the workspace is the place to do it. When you are using the System Browser, your tendency is to think top-down. It organizes the world in a top-down way. And yet, a lot of times, you're not sure exactly how you're going to partition things. So, the way we typically program, even the real expert programmers, is to do a lot of messing around in workspaces. And you use the local dictionary that's there for variables until you get a sense of where they really belong. And then a lot of times, you just copy your code into the Browser. Sometimes we call that sort of thing "scratch coding". Most people are not good enough to think top-down all the time. We certainly aren't good enough, so we think in a quick-and-dirty way in workspaces. We mention this because this style of development is not very apparent in this tutorial -- we're essentially presenting a "finished" program, so you don't really get to see how we got there.
Let's just finish this example. We have this 'history' instance variable, but again, it was initialized to nil. What we really want is to keep a list of balances, so we need to put an object in "history" that can hold a list of items in the order it receives them.
Click on "initialize" in the fourth pane of the browser (the message selector pane), add this new last line to the method, and accept it. Don't forget the period after the first line. I've added "history gets OrderedCollection new". OrderedCollection is a class in the system. And "new" is creating a new instance of it. OrderedCollections are a type of dynamic array. You can add new things to the end easily, and they just keep resizing themselves, indefinitely.initialize balance _ 0. history _ OrderedCollection new.
Now I'll come back to my workspace and select
and do Alt-d [Command-d] to "Do It". You see that over in the inspector, 'history' has an empty OrderedCollection in it. If there were something in the OrderedCollection, it would appear between the two parentheses. Our next job is to make something appear there. We want to add things to the history when the balance changes. There could be many places where the balance is changed, so let's create a single method that they all call. It's a choke-point for setting the balance -- any method modifying the balance should use this method so a history can be kept of all balance changes.
In this method, we store into the balance, and also append the new balance to the history. Now we need to modify the other two methods to use the 'balance:' method.balance: newBalance balance _ newBalance. history addLast: newBalance.
And now modify the other one.deposit: amount self balance: balance + amount.
Both of these methods do the same thing as before, but now, because they both use the "balance:" method to set the balance, they also append the resulting balance to the history. Let's try depositing stuff again. Paste this into your workspace.withdraw: amount amount > balance ifTrue: [ ^ self inform: 'sorry, not enough funds']. self balance: balance - amount.
b deposit: 100.
Select the 'history' variable in your inspector, then select "b deposit: 100" in your workspace and doIt again and again. As you do repeated deposits, you'll see the history build up in the inspector.
I'd like to see that history graphically. To do this, I'm going to add one more method to BankAccount.
| Note: if you use a Squeak before version 3.0, the following code should be used instead of that presented in the main text.
historyMorph | bars m | bars _ history collect: [:v | Morph new extent: 30@v]. m _ AlignmentMorph newRow centering: #bottomRight. m addAllMorphs: bars. ^ m
The goal is to produce a Morph containing a bar graph. Each bar is a generic Morph of the right size. The variables between vertical bar characters are being declared as local (temporary) variables. We have two of them, 'bars' and 'm'. 'bars' will be a collection of Morphs, whose heights are the bank balance at various times.historyMorph "displays barchart, width 30 per bar" | bars m | bars _ history collect: [:v | Morph new extent: 30@v]. m _ AlignmentMorph newRow hResizing: #shrinkWrap; vResizing: #shrinkWrap; cellPositioning: #bottomRight. m addAllMorphs: bars. ^ m
We send history the 'collect:' message. Collect makes a new collection the same size as the object it was sent to (history). The block, in square brackets, is evaluated once for each element of history. The value comes into the block local variable 'v'. A new Morph is created. '@' is an operator that makes an X-Y point from two numbers. You see that the width will be 30 and the height will be 'v'. 'extent:' tells the Morph what size to be. The result of the last expression in the block is what is put into the new collection. Bars ends up with a bunch of Morphs, one for each balance in history.
On lines 4-7, we make an AlignmentMorph to hold the bars. On the eighth line, we add the bars as submorphs. And finally we return the AlignmentMorph, m.
b historyMorph openInWorld.
Type these lines into the workspace. Select only the FIRST line and do a printIt. It returns an AlignmentMorph. Well, that's not very interesting, just printing it out, so I'm going to send the AlignmentMorph the message, openInWorld. When you execute the second line, it will cause the AlignmentMorph to get created and displayed on the screen. It looks like a bar graph.
In the next tutorial, we'll go into more about how Morphic works and how you can build graphical objects yourself. But this "bar graph" is basically a composite object which you can pick up and move around. It's got little objects in it. These are the Morphs of the individual bars. To manipulate these Morphs you need to get a Morphic halo (a set of colored dots surrounding the selected morph) by doing Alt-click [Command-click] on one of the bars. [CS 334: If Alt-click does not work, use the right mouse button.] Below the bar it says it's a Morph -- and it's a separately manipulable object. You can actually resize it with the yellow handle and even grab that bar out of there by picking it up by the black handle. or do other things to it. For any of the halo handles, if you pause over it with the cursor, it will bring up a balloon with information in it.
One more thing, to delete the entire graph, Alt-click [Command-click]
on the graph as a whole, away from a bar. Make sure it says AlignmentMorph
at the bottom. There's a little pink "X" label in the upper left part of
the halo - click on the X to delete the whole graph.
|Should you wish to leave your new World at some point, just bring up
its screen menu and select "previous project" -- this will return you to
the Squeak desktop where you were when you created this new Morphic Project
-- with the addition of a small window containing a thumbnail image of
your Morphic World. Just click on the thumbnail image to return to your
World at any time.
As you work through this tutorial, we recommend you save your work from time to time. You can do this - and indeed save the state of your entire system (the image) - by popping up the screen menu and selecting "save". When you are ready to close Squeak, select "save and quit" -- then, when you start Squeak again, it will open up exactly where you were when you quit.