Programming C, C++, Java, PHP, Ruby, Turing, VB
Computer Science Canada 
Programming C, C++, Java, PHP, Ruby, Turing, VB  

Username:   Password: 
 RegisterRegister   
 [WIP] Smalltalk Whirlwind
Index -> General Programming
View previous topic Printable versionDownload TopicSubscribe to this topicPrivate MessagesRefresh page View next topic
Author Message
wtd




PostPosted: Tue May 15, 2007 10:47 pm   Post subject: [WIP] Smalltalk Whirlwind

What is Smalltalk?

Smalltalk is a purely object-oriented programming language. It makes no compromises for other, earlier programming styles, and wholeheartedly embraces the notion of everything being an object.

Why should you care?

If you don't, then that's fine. If you want to expand your knowledge, then keep reading. Smile

What do you need?

Just download Squeak from http://www.squeak.org. It's available for several platforms, and free of charge.

First step

Launch Squeak. Feel free to read the material it puts up to start.

Then open a new workspace by clicking on the Tools tab at the right and dragging a Workspace out onto the desktop. The Workspace will be the center of our world as we explore the Smalltalk programming language.

Very simple expressions

All of your favorites are there. We can have integers, floating point numbers and strings.

Give it a try. Type something in the Workspace, select it, right-click and select "print it". Alternatively, you can use Alt-p. The expression will be parroted back at you in the Workspace.

Slightly less simple expressions

Of course, we can have things like mathematical expressions. Keep in mind, though, that Smalltalk is a simple language, and in this case, that means it doesn't respect order of operations in mathematical expressions, so you'll have to use parentheses to be explicit about that.

Messages, finally we get to the good stuff

In Smalltalk, everything is an object. Those objects are manipulated by sending messages to them, to which they respond.

For instance, we could find the absolute value of an integer by sending it a message.

code:
-1 abs


We actually conduct all math by sending messages. Adding two integers, for instance, is just sending the message + to one integer and providing it with an argument.

Sending a message to the result of another message is easy.

code:
-1 abs + 3




Another example of a message taking an argument would be finding the maximum of two numbers.

code:
(-1 abs + 3) max: 2


Let's try some output

First off, let's open a Transcript window. Go into Tools and drag out a Transcript. Now, back to the Workspace.

The Transcript class is an object itself, so we can send it messages. In this case, we'll send it the show and cr messages. In this case cr is short for "carriage return."

code:
Transcript show: 'Hello, world!'.
Transcript cr


Select this code in the Workspace and select "do it" from the menu. You should see a message appear in the Transcript window.

You'll note the period. This is used to end a statement. One is not necessary to terminate the final statement. We can simplify this though, since both messages are being sent to the same object.

code:
Transcript show: 'Hello, world!'; cr


A quick trip around the block

Blocks are important.

"Why?" you ask, and are perfectly justified in asking. It's a bold statement with nothing to back it up. Let me take a stab at that now.

Smalltalk is strictly evaluated. I can't send a message and expect execution of it to be delayed or even ignored. Not unless I use blocks. A block object can be thought of as essentially the same as an anonymous function in other languages.

A very simple block to add one to a number might look like the following.

code:
[ :x | x + 1 ]


And of course as this is an object, we can send it messages.

code:
[ :x | x + 1 ] value: 2


The above has a result of 3.

A meaningful example: conditionals

A conditional requires that depending on truth, one branch not be executed. Wecan only express this in Smalltalk with blocks.

code:
(4 > 2) ifTrue: [ 42 ] ifFalse: [ 27 ]


Here the comparison of 4 to 2 is sent the message ifTrue:ifFalse: to which two blocks are submitted as arguments. The result, since 4 is greater than 2 is 42.

Note that there is no special syntax here. There is only message passing.

While loops

As with conditionals, looping cannot be achieved without blocks. For a while loop, both the test and the body take the form of blocks.

For an infinite loop:

code:
[ true ] whileTrue: [ Transcript show: 'Foo!'; cr ]


Variables

While loops aren't much use without variables, so let's introduce them now.

code:
| counter |
counter := 10.
[ counter >= 0 ] whileTrue: [
   Transcript show: counter; cr.
   counter := counter - 1
]


Selecting all of the above, and then selecting "do it" will print a countdown from ten to zero.

You'll note that variable names are introduced in a clause before the code that deals with them. At the same time, their types are not declared.

Arrays and for loops

Array literals are present.

code:
#(3 4 2 1 5)


Looping over this is accomplished via messages and blocks.

code:
| arr |
arr := #(3 4 2 1 5).
arr do: [ :x |
   Transcript show: x; cr
]


Many would say that this removes the opportunity to keep track of the index, or requires something ugly.

code:
| arr index |
arr := #(3 4 2 1 5).
index := 1.
arr do: [ :x |
   Transcript show: index asString, ': ', x asString; cr.
   index := index + 1
]


But this is not necessary.

code:
| arr line |
arr := #(3 4 2 1 5).
arr doWithIndex: [ :x :i |
   line := i asString, ': ', x asString.
   Transcript show: line; cr
]


But surely by looping over the indexes instead of the elements, we can know when we're at the end of the array. This would let us do something after all but the last iteration. It turns out Smalltalk has an answer. Oh, and we'll sort the array for good measure.

code:
| arr |
arr := #(3 4 2 1 5).
arr sort do: [ :x | Transcript show: x ]
         separatedBy: [ Transcript show: ', ' ]


Messages can be sent to achieve other common tasks with minimal fuss using blocks. For instance, transforming one array into another.

code:
| arr1 arr2 |
arr1 := #(1 2 3).
arr2 := arr1 collect: [ :x | x + 1 ]


A quick note: the other way works too

If you really must use an incrementing looping idea.

code:
| arr output cur |
arr := #(3 5 6 1 2).
1 to: arr size do: [ :i |
   cur := arr at: i.
   output := (i = arr size) ifTrue: [ cur ] ifFalse: [ cur asString, ', ' ].
   Transcript show: output
]


code:

| arr1 arr2 |
arr1 := #(1 2 3).
arr2 := Array new: arr1 size.
1 to: arr1 size do: [ :i | arr2 at: i put: (arr1 at: i) + 1 ]


Let's define our own class of object

The following shamelessly rips off rdrake's C# whirlwind's Dog example, so we'll start by creating a BasicDog class.

First, open the System Browser. Do this by going into the Tools tab and dragging out a Browser. Here you'll see first the categories that classes in Squeak Smalltalk are organized into. We'll want to create our own category called "Dog-Example", so right-click and choose "add item" from the menu. This will prompt you for the same of a new category.

Once you've accepted that, you'll see the following in the lower pane of the window.

code:
Object subclass: #NameOfSubclass
        instanceVariableNames: ''
        classVariableNames: ''
        poolDictionaries: ''
        category: 'Dog-Example'


This is a template for a message we'll send to the Object class which will result in a new class being created.

code:
#NameOfSubclass


This is a symbol.

But anyway, we need to modify this to create our BasicDog class.

code:
Object subclass: #BasicDog
        instanceVariableNames: ''
        classVariableNames: ''
        poolDictionaries: ''
        category: 'Dog-Example'


To create the class, simply right-click and choose "accept" or simply Ctrl-s. An entry for it now appears.

But really, our BasicDog will have a few instance variables. Modify the code and then save it again.

code:
Object subclass: #BasicDog
        instanceVariableNames: 'givenName furColour'
        classVariableNames: ''
        poolDictionaries: ''
        category: 'Dog-Example'


And we should probably leave a comment. Something along the lines of:

Quote:
BasicDog is the root class for all of man's best friends.


Save that comment in the customary way.

Congratulations, you now have a class!

Convenience

Just to make things a little nicer, let's add a class message in a new category "object creation."

code:
newWithGivenName: n andFurColour: c
        "Create a new BasicDog object and initialize its variables"
       
        | r |
        r := super new.
        ^ r initWithGivenName: n andFurColour: c


Now, we can just use the following.

code:
| spike |
spike := BasicDog newWithGivenName: 'Spike' andFurColour: 'black'


Accessors

We still can't actually get any information out of our object. We'll have to change that by introducing a couple of new instance messages under the "accessors" category.

code:
givenName
        "Access a BasicDog's name"
       
        ^ givenName


code:
furColour
        "Access a BasicDog's fur colour"
       
        ^ furColour


There is no conflict with the names. All messages need an explicit receiver.

code:
| spike |
spike := BasicDog newWithGivenName: 'Spike' andFurColour: 'black'.
Transcript show: spike givenName, ' is ', spike furColour; cr


Subclassing is fun! So we'll do it again

Select the Dog-Example category again and you'll see a new class template again. This time we're subclassing BasicDog, but adding no new instance variables.

code:
BasicDog subclass: #Dog
        instanceVariableNames: ''
        classVariableNames: ''
        poolDictionaries: ''
        category: 'Dog-Example'


Now we just needs to implement some instance messages in an "actions" category.

code:
rollOver
        "A Dog rolls over"
       
        Transcript show: self givenName, ' rolled over.'; cr


code:
makeNoise
        "A Dog makes noise"
       
        Transcript show: 'Bark!'; cr


And finally, a Dog can print information about itself.

code:
printInfo
        "Print out name and fur colour info"
       
        Transcript show: 'The fur colour of ', self givenName,
                         ' is ', self furColour, '.'; cr


One more time! Or, "the beagle did it"

code:
Dog subclass: #Beagle
        instanceVariableNames: ''
        classVariableNames: ''
        poolDictionaries: ''
        category: 'Dog-Example'


code:
makeNoise
        "A Beagle makes a different noise"
               
        Transcript show: 'Howl!'; cr


That's all well and good, but... boring

So far we've seen text. Now, text rocks my world, but it's not everyone's cup of tea, so how about some graphics?

In a Workspace type and execute the following.

code:
Morph new openInWorld


You'll now see a blue square on the screen. You can drag it around, and if you right-click you'll see a menu. Select "select" and the Morph you've created will be surrounded with buttons representing actions that can be carried out on the Morph.

But let's create our own Morph subclass in category "My-Stuff".

code:
Morph subclass: #TestMorph
        instanceVariableNames: ''
        classVariableNames: ''
        poolDictionaries: ''
        category: 'My-Stuff'


And now we can create a new TestMorph.

code:
TestMorph new openInWorld


Making it do something

Now, our Morph does quite a bit, precisely because it is a Morph and has a lot of functionality for free as a result. But, let's make it do something we could not otherwise have had it do.

Let's first add a message.

code:
growInSize
        | b |
        b := self bounds.
        self bounds: (Rectangle origin: 0@0 corner: (b width + 5) @ (b height + 5))


Here we have a temporary variable b which refers to the rectangle formed by the bounds of the Morph. We use that to create a new rectangle which defines the bounds after the Morph has grown in size.

But, now we need some way to trigger this growth. Why not use a mouse event?

First we have to make the Morph understand that it can handle mouse down events. So we implement the handlesMouseDown message which overrides that in Morph.

code:
handlesMouseDown: evt
        ^ true


Next we'll need to implement the mouseDown message, which overrides the same message in Morph.

code:
mouseDown: evt
        self growInSize


With all of this saved, we need only launch a new TestMorph and click on it. Each time you click, it grows by five pixels in each direction.

What happened to my right-click?!

Used to be we could right-click our Morph, "select" it, and do things to it. Now we just get a change in size. We can still use Ctrl and click to get a menu, but that's not for the lazy.

We'll have to make a change.

code:
mouseDown: evt
        evt redButtonChanged ifTrue: [
                self growInSize
        ] ifFalse: [
                super mouseDown: evt
        ]


If the red button (left button) is clicked we send the growInSize message. Otherwise we resend the mouseDown message to super.

Dynamic growth

What if we want to grow our Morph at an increasing rate? That is, it'll grow by more each time the mouse is clicked. Well, we'll need to have our TestMorph class keep track.

code:
Morph subclass: #TestMorph
        instanceVariableNames: 'growthFactor'
        classVariableNames: ''
        poolDictionaries: ''
        category: 'My-Stuff'


code:
initialize
        super initialize.
        growthFactor := 1.
        ^ self


code:
growInSize
        | b |
        b := self bounds.
        self bounds: (Rectangle origin: 0@0 corner: (b width + growthFactor) @ (b height + growthFactor)).
        growthFactor := growthFactor * 2
Sponsor
Sponsor
Sponsor
sponsor
Clayton




PostPosted: Wed May 16, 2007 5:24 pm   Post subject: RE:[WIP] Smalltalk Whirlwind

Excellent stuff as usual wtd! I'm not entirely sure that I understand exactly what Smalltalk is all about, but it definitely sounds like it's worth trying out Very Happy
Cervantes




PostPosted: Wed May 16, 2007 6:52 pm   Post subject: RE:[WIP] Smalltalk Whirlwind

I didn't know Smalltalk was so syntaxless. Thanks for this, wtd!
Display posts from previous:   
   Index -> General Programming
View previous topic Tell A FriendPrintable versionDownload TopicSubscribe to this topicPrivate MessagesRefresh page View next topic

Page 1 of 1  [ 3 Posts ]
Jump to:   


Style:  
Search: