Damian Nowak is a CEO at Virtkick. He's a Ruby coder, an Arch Linux hacker, and drinks good beer.
Niedawno, w ramach zajęć z przedmiotu Języki programowani na mojej uczelni miałem przyjemność poznać w pełni obiektowy język programowania Smalltalk.
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.
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]