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ż:


Jest już 5 komentarzy ↓
Zyx // sty 18, 2009 at 15:56
Hehe, znam to uczucie, bo nas z kolei uraczyli w tym semestrze Lispem. Niestety, w tym wypadku uczelnia mnie do niego zraziła – i nie tylko mnie. Jeszcze nie spotkałem na roku osoby, która by się wyrażała pozytywnie o sposobie organizacji zajęć z przedmiotu „obliczenia symboliczne”…
W Lispie jedna rzecz mnie zastanawia – pierwszy raz spotykam się z językiem, który służy do przetwarzania list i jednocześnie nie zawiera części elementarnych operacji na nich (np. normalne usuwanie elementu z listy).
BTW. Na AGH w ramach „języków programowania” uczymy się normalnych języków :). Rzeczy typu Lisp czy Prolog wprowadzane są w ramach innych przedmiotów zależnie od potrzeb i ich zastosowań.
Nowaker // sty 18, 2009 at 16:12
Lispa też mieliśmy na tym przedmiocie, ale nie było żadnego projektu do napisania w nim – to tak, jak by go nie było ;-) Mieliśmy za to Prolog i niestety, dla mnie Prolog to porażka. Od strony rozumienia. Jeśli się żyje predykatami, to pewnie jest OK.
wh // sty 18, 2009 at 16:26
gigamonkeys.com@zyx: „W Lispie jedna rzecz mnie zastanawia – pierwszy raz spotykam się z językiem, który służy do przetwarzania list i jednocześnie nie zawiera części elementarnych operacji na nich (np. normalne usuwanie elementu z listy).”
http://www.gigamonkeys.com/book/they-called-it-lisp-for-a-reason-list-processing.html Może po prostu trzeba było chodzić na wykłady?
gosc // paź 17, 2010 at 21:16
Dzięki, przydało się.
Adam // paź 16, 2011 at 19:13
Dzieki, ludziom z eti sie przydalo
Zostaw swój komentarz