Damian Nowak is a CEO at Virtkick. He's a Ruby coder, an Arch Linux hacker, and drinks good beer.

Wprowadzenie do SmalltalkaJanuary 2009

Niedawno, w ramach zajęć z przedmiotu Języki programowani na mojej uczelni miałem przyjemność poznać w pełni obiektowy język programowania Smalltalk.

Pierwsze wrażenie

Pierwszym akronimem, który rzucił się do głowy po kilku minutach patrzenia na przykład kodu napisanego w Smalltalku, był wtf ;-) Wszystko przez składnię. Od dziecka mamy tylko styczność z kodami podobnymi albo do Pascala (Ada, Python) albo do C (PHP, Java). Smalltalk raczy nas całkowicie odmienną składnią, zatem aby zacząć cokolwiek w nim pisać, trzeba spędzić trochę czasu na zrozumienie zasad. Zwłaszcza, że polski internet nie powie nam praktycznie niczego mądrego na temat składni Smalltalka.

Wielokat subclass: #Trojkat
        instanceVariableNames: ''
        classVariableNames: ''
        poolDictionaries: ''
        category: 'JezykiProgramowania'!

!Trojkat methodsFor: 'initialize-release'!

        initialize: bok
                "Tworzy trójkąt równoboczny o podanym boku"

                | h |
                h:=bok * 3 sqrt / 2.
                super initialize: 3 name: 'Trojkat'.
                wierzcholki at: 2 put: bok@0.
                wierzcholki at: 3 put: bok/2@h.
        !
!

| t |
t:=(Trojkat new) initialize: 10.

Wyjaśnienie składni

Z góry ostrzegam, iż moje wyjaśnienie składni na pewno nie jest kompletne, ani w 100% poprawne. Przedstawiam tylko własne doświadczenia z poskramiania składni Smalltalka. Przepraszam za słownictwo, które będą używał. Nie będzie ono specyficzne dla Smalltalka, tylko języków spokrewnionych z C++ i Javą.

Poloneza czas zacząć. Zadeklarujmy klasę wielokąt dziedziczącą po klasie bazowej Object.

Object subclass: #Wielokat
        instanceVariableNames: 'wierzcholki nazwa '
        classVariableNames: ''
        poolDictionaries: ''
        category: 'JezykiProgramowania'
!

instanceVariableNames definiuje nazwy wszystkich tzw. „zmiennych wystąpienia”, skądinąd znanych jako properties. classVariableNames odnosi się do zmiennych klasowych, znanych z języka C++ jako statyczne.

Mamy klasę, więc pora na konstruktor. ~~~~ !Wielokat methodsFor: ‘initialize-release’!

initialize: liczbaWierzcholkow name: nowaNazwa
    "konstruktor tworzy nowy obiekt wielokąta"

    nazwa:=nowaNazwa.
    wierzcholki:=Array new: liczbaWierzcholkow.
    wierzcholki at: 1 put: 0@0.
!

! ~~~~

Do metod typu „initialize-release” zaliczają się konstruktory i destruktory. Tajemnicze wykrzykniki zamykają nam sekcje (i czasem też otwierają). Wcięcia kody wykonałem w taki sposób, aby łatwo zrozumieć działanie wykrzyknika. Przypomina on tutaj działanie { i } z języka C. Każde wyrażenie jest zakończone kropką, co jest ekwiwalentem średnika w językach podobnych do C. Poprzez „initialize: liczbaWierzcholkow name: nowaNazwa” zadeklarowaliśmy nową metodę, która przyjmuje dwa parametry - „initialize” oraz „name”. Ciąg znaków w cudzysłowach to komentarz. Dalej napotykamy na „wierzcholki:=Array new: liczbaWierzcholkow”. Tworzymy nowy obiekt typu Array o określonej wielkości. W pierwszym elemencie tablicy umieszczamy współrzędne x oraz y wierzchołka (x@y). W Javie byśmy napisali wierzcholki[0] = new Point(0, 0).

!Wielokat methodsFor: 'accessing'!

    nazwa
        "Podaje nazwę wielokąta"
        ^nazwa
    !

    nazwa: nowa_nazwa
        "Ustawia nazwę wielokąta"
        nazwa:=nowa_nazwa
    !

!

Metody typu „accessing” to metody dające dostęp do domyślnie prywatnych zmiennych wystąpienia (properties). Metoda „nazwa” bez parametrów zwraca nam nazwę figury - wartości zwracamy operatorem ^. Dodanie parametru przy wywołaniu metody „nazwa” spowoduje ustawienie nowej nazwy.

Mamy już klasę figura, pora więc stworzyć trójkąt, która dziedziczy implementację po wielokącie. ~~~~ Wielokat subclass: #Trojkat instanceVariableNames: “ classVariableNames: ” poolDictionaries: “ category: ‘JezykiProgramowania’ !

!Trojkat methodsFor: ‘initialize-release’!

initialize: bok
    "Tworzy trójkąt równoboczny o podanym boku"

    | h |
    h:=bok * 3 sqrt / 2.

    super initialize: 3 name: 'Trojkat'.
    wierzcholki at: 2 put: bok@0.
    wierzcholki at: 3 put: bok/2@h.
!

! ~~~~

Zapis | h | oznacza deklarację zmiennej lokalnej o nazwie „h”. Nie jest to niestety obliczenie wartości bezwzględnej z liczby h ;)

!Trojkat methodsFor: 'actions'!

    pole
        "Liczy pole trójkąta równobocznego"
        ^(wierzcholki at: 2) x squared * (3 sqrt) / 4.
    !
!

Metody typu „actions”, to wszystkie typowe metody, które wykonują dla nas jakieś czynności i zwracają jakiś wynik. Zapis liczba squared w Smalltalku oznacza to samo, co liczba.squared() w językach C-podobnych.

Przy okazji należy też zwrócić uwagę na priorytety operatorów. W Smalltalku nie działają one zgodnie z regułami matematyki, lecz w ogólnym przypadku od lewej do prawej (gwoli ścisłości - istnieją jakieś priorytety, jednak nie wiadomo mi zbyt wiele na ich temat).

Nie jest to jednak nielogiczne. Bierze się to stąd, że dodawanie również jest metodą. liczba:=2 + 3 * 4 można by przełożyć taki pseudokod: liczba = 2.dodaj(3); liczba = liczba.pomnoz(4);.

Zapis 3 sqrt nakazuje liczbie 3 obliczenie swojego pierwiastka i zwrócenie go. Widać więc, iż „zwykły int” jest również obiektem. Zapis (wierzcholki at: 2) x oznacza nic innego, tylko wierzcholki[1].x. Zrozumienie tego zapisu zajęło mi prawie godzinę ;-)

Teraz dodajmy działania na naszym trojkącie. ~~~~ !Trojkat methodsFor: ‘arithmetic’!

+ figura
    "Dodaje dwie figury w sensie pola"

    | p b |

    p:=self pole + figura pole.
    b:= (4 / 3 * p * (3 sqrt)) sqrt.

    ^(Trojkat new) initialize: b.
!

- figura
    "Odejmuje dwie figury w sensie pola"

    | p b |

    p:=self pole - figura pole.

    (p < 0)
        ifTrue:
        [
            p:=figura pole - self pole.
            ^(Trojkat new) initialize: (4 / 3 * p * (3 sqrt)) sqrt.
        ]
        ifFalse:
        [
            ^(Trojkat new) initialize: (4 / 3 * p * (3 sqrt)) sqrt.
        ]
!

Metody typu „arithmetic” stanowią przeciążenie operatorów arytmetycznych funkcji - dodawania, odejmowania, mnożenia, dzielenia i pewnie innych, których istnienia nie jestem świadom ;) Nie wiem, czy operatory porównania (np. „<”) to również operatory arytmetyczne, czy może inne, ale je również da się przeciążyć. Zapisem **(p < 0)** nakazujemy obiektowi **p** porównać się z liczbą 0 (która też jest obiektem). Następnie w zależności od wyniku porównania wykonujemy odpowiednie czynności. W moim przykładzie nie chcę zwracać ujemnej wartości, w związku z czym liczę nową wartość **p**, odejmując tym razem liczbę mniejszą od większej.

Wypadałoby powiedzieć trochę więcej na temat bloków kodu. Blok kodu zamknięty pomiędzy znaki **[** oraz **]** to specjalna klasa o nazwie **BlockClosure**. Dzięki niej można uzyskać współbieżność, jeśli na końcu bloku dodamy słowo kluczowe **fork**. Tak mówi teoria i pan profesor na wykładzie, jednak mi nie udało się uzyskać tego ;) Pewnie zbyt mało próbowałem albo wykonuję jakiś błąd syntaktyczny.

Dokończmy działania na trójkącie i pokażmy, czy program żyje.
* figura
    "Mnoży dwie figury w sensie pola"

    | p |

    p:=self pole * figura pole.
    ^(Trojkat new) initialize: (4 / 3 * p * (3 sqrt)) sqrt.
!

/ figura
    "Dzieli dwie figury w sensie pola"

    | p |

    (figura pole = 0)
        ifTrue:
        [
            ^0.
        ]
        ifFalse:
        [
            p:=self pole / figura pole.
            ^(Trojkat new) initialize: (4 / 3 * p * (3 sqrt)) sqrt.
        ]
!

!

| t | t:=(Trojkat new) initialize: 10. Transcript show: (‘Powinno wyjsc cos kolo 43’) printString; cr. Transcript show: (t pole) printString.


Klasa Trascript służy do wypisywania danych na konsolę. Może wygląda troszkę dziwacznie, no ale cóż zrobić. **cr** odpowiada za znak nowej linii.

Mam nadzieję, że moje amatorskie wprowadzenie do Smalltalka pomoże komuś przełamać pierwsze lody przy poznawaniu tego ciekawego języka.


Odwiedź również:

+ [ObjectSpace.net][os]



Damian Nowak
CEO & Ruby Developer