A Smalltalk programozási nyelv

Smalltalk Tutorial

Ebben a fejezetben tutorialok találhatóak első programjaink megírásához.

A kódok egy része működhet minden környezetben, de javasolt a különböző implementációk sajátosságaihoz igazodva egyéni tutorialok készítése.

Általános (ANSI standard) Smalltalk Tutorial

Egy általános Smalltalk nyelvi tutorial, kommentezett futtatható kódokkal.
Használatához valamilyen futtatókörnyezetbe másolandó a kód. Tesztelése és fejlesztése a Pharo 1.3 környezettel történt.
Tutorial letöltése
Készült: 2012
Készítő: Ormándi Mátyás
Az ANSI standard dokumentációjának feldolgozás

Pharo

A Pharo futtatókörnyezet egy 2009-es fejlesztésű, kényelmes grafikus felületet nyújtó framework. Elérhetősége: Hivatalos oldal
Innen letölthető, és kicsomagolás után már használható is.
A példaprogram futtatásához új workspace nyitása után oda bemásolandó a futtatható kód, majd kijelölés után az egyes részek Crtl+D vagy jobb klikk + Do it utasítással történnek meg az üzenetküldések.

Dolphin Smalltalk Tutorial

A következő tutorial a Dolphin Smalltalk X6 (kereskedelmi program) használatához készült. Egy része a helpjének fordítása, tartalmaz tapasztalati tippeket, illetve példán keresztül mutatja meg a programkészítést.

A 8.2. pontban már találkozhattunk a Dolphin implementációval, bemutatásra kerültek a környezet sajátosságai, a beépített browser ablakok.

Gyorstalpaló:

  1. A fejlesztői környezet indítása után használjuk a File > New Workspace menüpontot. Ekkor megjelenik egy szövegszerkesztő (Workspace window, a kód is szöveges formában menthető).
  2. A kód bármely részlete futtatható ha kijelöljük, kommentezésre használhatjuk az ""-t, és használjuk a .-t, mint utasítás-elválasztót (nem utasítás-lezáró).  
        futtatás: ctrl+E , futtatás és megjelenítés: ctrl+D (a Workspace menüből érhetőek el (8.2.2.pont))
  3. Írjuk be, pl.:
    "első programom" 'hello world'. 3+4
  4. Jelöljük ki külön-külön a második illetve a harmadik sort, majd üssünk Ctrl+D-t. A sor végén megjelenik a kimenet. Ne felejtsünk el ezért egy DEL-t ütni, hogy ne rontsuk el a kódunkat.

Hasznos dolgok:

Csomag (package) készítése:

A Smalltalk implementációk fejezetben a Package Browser kapcsán esett szó a PAC részletes leírásáról.

Ami fontos számunkra, ha elkezdünk package-t használva programot írni:

Miért jó package-t használni a program szerkesztés szempontjából?:

Érdemes párhuzamot vonni, hogy a modernebb objektum-orientált programozási nyelvekhez készített környezet alapján könnyen eligazodjunk:

package-készítés

modularizáció, projektkészítés

package browser

project class-browsere, a project osztályainak azonosítására

class browserrel üzenetek szerkesztése

mintha a class-browserrel metódusokat szerkesztenénk

    Az utolsó pontnál kiemelhetjük azt a párhuzamot, hogy ha grafikus programot készítünk, és ablak jellegű (pl. ShellView) osztályból származtatunk, akkor az "eseménykezelőket" megírhatjuk úgy, mint az ősosztály üzeneteinek felüldefiniálása.

Package használata, osztályok hozzáadása a package-hez:

10.1.3. Példa Program készítése:

A programunkhoz készítünk egy saját package-t, installáljuk a rendszerbe. Az osztályok hozzáadása már mind a grafikus felületen keresztül történik. A programunk futtatása a package megjelölt osztályának a példányosításával történik. A kód írását a Class-Browser-ben végezzük, itt készítjük el az osztályainkat, a származtatás jól látszik a fa-diagrammban, itt hozzuk létre az üzenetek kódjait is.

Eszközök: ablakhasználat, egér használat (drag and drop), alakzatok rajzolása (osztály hierarchia).

Forrás

bead.pac



Végső kinézet:









Squeak Smalltalk Tutorial:

Tilitoli példaprogram

Forrás

Smalltalk_tilitoli.zip

A feladat

A feladat egy tilitoli program készítése. A program egy ablakból áll, azon van 4x4 kocka, ezek egyike a lyuk. A lyuk tologatásával mozognak a kockák is (bármely, a lyukkal szomszédos kockát ki lehet cserélni a lyukkal: ehhez a kockára kell kattintani). A kockák mellett az ablakban van egy gomb, ami kiírja, minden kocka jó helyen van-e, egy másik, ami megkeveri a kockákat, és egy harmadik, amin az látható, hogy a legutóbbi keverés óta hány lépés (csere) történt.

Képernyőkép

A terv

A program öt osztályt tartalmaz:

A megvalósítás érdekesebb részei

Az osztályok kinézete

Az osztályok deklarációja eléggé hasonlít egymásra, így csak a HButtont írjuk ide példaként. A HButton a BasicButton leszármazottja, és azt a megadott adattagokkal terjeszti ki. A (posx,posy) az aktuális pozíció, a (goalx,goaly) a kocka helye. Az m a tartalmazó mátrix, az imagem a tartalmazott kép, a tt a tartalmazó TT osztályú objektum.

BasicButton subclass: #HButton instanceVariableNames: 'posx posy goalx goaly m imagem tt' classVariableNames: '' poolDictionaries: '' category: 'TiliToli'
A logikai réteg néhány függvénye a HButtonban

A kocka inicializálásakor a ttInitx:y:m:prefix:postfix:postfix:tilitoli: metódust kell meghívni (amit a TT meg is tesz). A képfájlnevek mind prefix(i)postfix alakúak, ahol az (i) 0 és 15 közt végigmegy. A self visszaadása nem fontos, nem használjuk fel máshol.

"HButton:" ttInitx: xa y: ya m: ma prefix: prefix postfix: postfix tilitoli: tta "a kocka kezdeti beállítása" "(x,y): kezdeti (és cél) koortináta" "m: a tartalmazó mátrix" "prefix, postfix: a képfájlnevek prefixe és postfixe" | index picFileName | tt := tta. m := ma. posx := xa. goalx := xa. posy := ya. goaly := ya. index := (posy-1)*4+(posx-1). self label: index asString. self width: 50; height: 50; useSquareCorners. picFileName := Text fromString: prefix. picFileName append: (index asString). picFileName append: postfix. imagem := (Form fromFileNamed: (picFileName asString)) asMorph. self addMorph: imagem. ^self

Az osztály legbonyolultabb függvénye is csak egy elágazást tartalmaz. Felhívjuk a figyelmet arra, hogy az and: és or: üzenetek előtt a kifejezések kerek zárójelben vannak, míg utánuk szögletesben ((valami) and: [valami]).

"HButton:" trySwapx: otherX y: otherY "Megpróbálja megcserélni az adott kockát az (otherX,otherY) koordinátájú kockával. Ha ez nem legális, akkor nem történik semmi. " | other myX myY | (((m at: otherY at: otherX ifInvalid: false) ~= false) "létezik-e a (tryX,tryY) mezo?" and: [((m at: otherY at: otherX) zero) or: [self zero]]) "a (tryX,tryY) mezon zero van, vagy én zero vagyok?" ifTrue: [ myX:=posx. myY:=posy. other := m at: otherY at: otherX. "megcseréljük a koordinátáikat" other posx: myX posy: myY. self posx: otherX posy: otherY. "megcseréljük a gombokat a mátrixban" m at: myY at: myX put: other. m at: otherY at: otherX put: self. "beállítjuk a TT-ben a " (self zero) ifTrue: [ tt holeX: otherX holeY: otherY. ] ifFalse: [ tt holeX: myX holeY: myY. ]. "pozíciók újraszámolása" self reComputePosition. other reComputePosition. tt swapHappened. ^true. ]. ^false.

A recomputePosition újraszámolja a kocka pozícióját a képernyőn, és arrébb megy, ha kell. A left üzenettel arra kérjük, hogy mozduljon el úgy, hogy az ő bal széle adott távolságra legyen a tartalmazójának a bal szélétől. A top hasonló elven működik.

"HButton:" reComputePosition "a (posx,posy) alapján újraszámolja a pozícióját a képernyőn, és arrébb megy, ha kell" self left: ((tt left) + ((posx-1)*50)+10). self top: ((tt top) + (tt labelHeight) + ((posy-1)*50)+10). ^self
Az egérkattintások lekezelése a HButtonban

Az egérkattintásokat a kockákban le kellett kezelnünk. Ehhez felül kellett definiálnunk a handlesMouseDown metódust, mivel ez a metódus adja vissza azt, hogy az adott osztály objektumai le szeretnék-e maguk kezelni a kattintásokat (alapból hamisat ad vissza). Mivel mi le szeretnénk kezelni, ezért egy igaz értéket kell visszaadnia a metódusnak:

"HButton:" handlesMouseDown: e "igen, le szeretnénk kezelni a MouseDown eseményt" ^true

Az esemény valódi lekezelése pedig a mouseDown-ban történik. (Amikor rendszer érzékeli, hogy rákattintottak az objektumra, megkérdezi tőle, hogy szeretné-e lekezelni, és ha igen, akkor meghívódik ez a metódus.)

"HButton:" mouseDown: evt "ha lenyomták az egeret, akkor megpróbálunk cserélni a szomszédokkal" ^self trySwap.
A TT osztály inicializációja

A TT legbonyolultabb metódusa az initPrefix:postfix:, ami inicializálja. Ez létrehoz egy 4x4-es m mátrixot, és ebbe cellánként belerakja a megfelelően beállított kockákat. Ezután a három gombot is beállítja.

"TT:" initPrefix: pre postfix: post "inicializálás" "prefix, postfix: a képfájlnevek prefixe és postfixe" | b | self width: 350; height: 220. m := Matrix new: 4. holeX:=1. holeY:=1. 1 to: 4 do: [:y | "sor" 1 to: 4 do: [ :x | "oszlop" b := HButton new. b ttInitx: x y: y m: m prefix: pre postfix: post tilitoli: self. self addMorph: b. b reComputePosition. m at: y at: x put: b. ]. ]. state := TTStateTextMorph newM: m. self addMorph: state. state left: 250; top: (self labelHeight + 10). state refresh. counter := TTCounter new. self addMorph: counter. counter setZero. counter left: 250; top: (self labelHeight + 10) +20. randomizeButton := TTRandomizeButton newTT: self. self addMorph: randomizeButton. randomizeButton left: 250; top: (self labelHeight + 10) +40. self openInWorld. ^self.

A TT osztályok létrehozása a newPrefix:postfix: osztályszintű üzenettel történhet. Itt meg kell adnunk a használandó image-fájlok prefixét és postfixét. Ez a függvény meghívja az előző, TT objektumokat inicializáló függvényt.

"TT:" newPrefix: pre postfix: post "egy új TT objektum létrehozása" | o | o := TT labelled: 'Tilitoli (Hoch Csaba)'. o initPrefix: pre postfix: post. ^ o.
Collapse/expand kezelése

A collapse és expand azt jelenti, hogy az ablak összemegy, illetve eredeti méretére visszaméreteződik. Ez akkor történik, ha rákattintunk a jobb felső sarkában lévő körre. Egy ablak alapértelmezett viselkedése, hogy összemegy, illetve visszaméreteződik, de a tagjaiban semmi nem változik (azaz az ablak összecsukódik, de pl. a kockák továbbra is láthatóak). Az elvárt viselkedés viszont az egy programmal szemben, hogy a collapse eseménykor az összes gomb, ami az ablakon van, tűnjön el, az expand eseménykor pedig jelenjenek meg.

Ehhez felül kellett definiálni a TT-ben az ősosztály collapse, collapseOrExpand és expand metódusait (ezek hívódnak meg, amikor a felhasználó a körre kattint). Mindegyik esetben végrehajtjuk az ősosztály tevékenységét (azaz maga az ablak összehúzódik vagy visszaméreteződik), majd ezután végrehajtjuk a gombok elrejtését, illetve megjelenítését is.

Példaként a collapseOrExpand függvényt írjuk le itt, a többi is hasonló:

"TT:" collapseOrExpand super collapseOrExpand. self collapseOrExpandSubMorphs. "TT:" collapseOrExpandSubMorphs isCollapsed ifFalse: [ state show. counter show. randomizeButton show. m do: [ :b | b show. ] ] ifTrue: [ state hide. counter hide. randomizeButton hide. m do: [ :b | b hide. ] ]

Megjegyzés

A program elkészítéséhez a Squeak 3.9 rendszert (interpreter és fejlesztőkörnyezet) használtuk.

GNU Smalltalk Tutorial

A tutorial készítéséhez fel lett használva az alábbi forrás:

http://bioskop.fr/gtk_tutorial/ch-GettingStarted.html

A tutorial Linux (Kubuntu 12.04) alatt készült, Windows alatt a forrásfájlokat nyitó megjegyzésre nincsen szükség.

Az egyszerű hello world program a GNU Smalltalk parancssoros környezeténél már bemutatásra került, így itt első lépésként jelenítsünk meg egy üres ablakot. Ez annyira nem tud semmit, hogy a bezáró gomb ugyan működik rajta, de magát a programot parancssorból kell majd kilőni. (Ennek a használt GTK az oka, lsd. később.) Az Eval objektum végrehajtja a blokkban lévő utasításokat.

#!/usr/bin/env gst Eval [ PackageLoader fileInPackage: 'GTK'. window := GTK.GtkWindow new: GTK.Gtk gtkWindowToplevel. window show. GTK.Gtk main ]

Mentsük el a forráskódot egy st kiterjesztésű fájlba. Futtatása a gst fájlnév paranccsal történik. Az eredmény:

Egyszerű ablak

Készítsük el az első saját osztályunkat (HelloWorld)!

A Smalltalkban minden osztály az Object leszármazottja, ezt a megszokottal ellentétben ki is kell írnunk. Új osztályt a subclass üzenettel hozhatunk létre. Az osztálynak két változója lesz, egy ablak (window), illetve egy gomb (button), amelyet majd elhelyezünk az ablakban. Mivel GTK-val dolgozunk, használat előtt be kell töltenünk a szükséges csomagot, ez található az osztály előtti Eval-ban.

Szintén a GTK következménye, hogy szükségünk van egy destroy nevű metódusra, amely az ablak bezárásakor hívódik majd meg, és amely a GTK tudtára adja, hogy nincs tovább szükségünk a létrehozott ablakra. Ezen kívül feltétlenül szükségünk lesz még egy metódusra, ami létrehozza és megjeleníti az ablakot. Ezt show-nak szokás elnevezni.

A show-ban létrehozunk egy ablakot, mint ahogy az előző példában is tettük, valamint egy gombot. Ezen kívül három példát is láthatunk események és eseménykezelők összekapcsolására. A connectSignal segítségével összekötjük az ablak bezásárakor keletkező destroy eseményt az általunk írt destroy metódussal, illetve a gomb click eseményét a szintén általunk írt hello metódussal, amely kiír egy üzenetet a standard kimenetre, valamint a destroy metódussal. Így a gomb kiír a standard kimenetre, valamint bezárja a programot. Végül hozzáadjuk a gombot az ablakhoz, és megjelenítjük őket.

Az osztály definíciója után már csak még egy Eval blokkra van szükségünk az osztály példányosításához, illetve az ablak megjelenítéséhez és a GTK elindításához.

#!/usr/bin/env gst Eval [ PackageLoader fileInPackage: 'GTK'. ] Object subclass: HelloWorld [ | button window | hello [ 'Hello World' printNl ] destroy [ 'destroy signal occured' printNl. GTK.Gtk mainQuit ] show [ window := GTK.GtkWindow new: GTK.Gtk gtkWindowToplevel. window connectSignal: 'destroy' to: self selector: #destroy userData: nil. window setBorderWidth: 10. button := GTK.GtkButton newWithLabel: 'Hello World!'. button connectSignal: 'clicked' to: self selector: #hello userData: nil. button connectSignal: 'clicked' to: self selector: #destroy userData: window. window add: button. button show. window show ] ] Eval [ hello := HelloWorld new. hello show. GTK.Gtk main ]

A program futásának eredménye:

Egyszerű ablak gombbal