Vlastni navrh jazyka je celkem zajimavy. V lispovi jsme tak nejak svevolne dlouhym vyvojem dosli k tomu ze 70% jeho knihoven je objektovych. Jeho typy maji stromovou strukturu a celkove to hodne pripomina plne objektovy jazyk. (alespon nektere moderni implementace) Proto se autori rozhodli udelat jazyk co by narozdil od lispu byl objektovy uz od zacatku proste mel to ve vlastni implementaci jazyka. To by melo zjednodusit kompilaci protoze by tam nemusely takove 50 let stare archaismy jako v lispovi. Vsechny jeho konstrukce by byly objektove ne jako v lispu jenom cast kde se to hodi tudiz by byly jednodusi na zvladnuti jednotnejsi a rezsiritelnejsi. Jak se jim to povedlo posudte samy:
'Hello, world' printNl !
Tady vidite ze format vyhodnocovatelne formy se zmenil. Hlavni uprava
spociva v tom ze jako prvni se pise objekt se kterym neco delate - v
tomto pripade 'Hello, world' ktery reader precte do stringoveho objektu.
Jake druhe je zprava co mu chcete poslat tedy printNl coz je ocividne
zprava co ho ma vypsat. ! oznacuje konec formy a znamena neco jako run v
basicovi neboli ted to odstartuj. Takze v tomto pripade ctecka narazi na
prvni cast a vytvori objekt hello word tedy instanci tridy text ktere
posle zpravu.Takze jak vidite lispovske objekty byly nahrazeny plnokrevnymi objekty co jsou schopne prijimat zpravy a byl zmenen format formy a pridan znak ! na odpaleni- muzete si napsat vic forem a treba pet naraz odpalit. Na to v lispovi potrebujete blok. Taky zavorek ubylo.
12345 printNl !
A integer se nam vytiskne(plus hodne statistik kolem-to se da vypnout
pomoci prepinace -q) Jak vidite nic se nezmenilo oproti lispovskemu
print jenom poradi se prohazelo. No a matematika stejne jako v lispu
neni nic special. Proste napisete:
(1 + 2) printNl !a vipise se 3. Jak to funguje? () jsou neco jako [] v tcl nebo () v lispovi proste umoznuji vnorovani forem a rikaji ze se to ma vyhodnotit napred. Nejprve se tedy vyhodnocuje forma 1 + 1. To je poslani zpravy + objektu 1. a navic predani parametru 1. Ta samozdrejme objekty secte a vysledkem je objekt 3 ktery vrati. Navratova hodnota tedy 3 se dosadi za () a vyvola se: 3 printNl !. Jeste je dulezita tahle konstrukce+
(1 + 3 + 4 - 5) printNl !Tady interpretr veme 1 jako vychozi objekt a zjisti ze je treba poslat zpravu + zjisti ze zprava + ma 1 parametr posle ji tedy objekkt 3 vysledek bude objekt 4 a bude pokracovat na(4 + 3 -5) tedy mu posle zpravu + z jednim parametrem 3 atd.. To ma vyhodu ze se da pekne retezit zpravy. Prepis do lispu je:
(+(+(+ 1 3) 4) 5)Coz ten smalltalkovej vypada lip protoze se to pise tak jak vyhodnocuje. Na druhou stranu tenhle postup komplikuje volitebny pocet parametru protoze nikdy neni jasne kde konci volani prvni zpravy a zacina druhe. Mimochodem je to dalsi celkem zbytecna jazykova konstrukce spolu z () a ! Nahodou matematika vypada lidsky. Je tu ale problem z upredostnovanim protoze zpravy se posilaji od leva do prava vyraz:
(1 + 3 / 2) printNl !se vyhodnoti do 2 misto 1 a 3/2. Nastesti zavorky funguji vlastne stejne jako matematicke zavorky a:
(1 + ( 3 / 2 )) printNl !uz bude fungovat.
Smalltalk at: #x put: 0 !
Casem popisu proc tak divne. Je to treba abychom mohli priradit jmeno.
Mimochodem protoze se parametry nevyhodnocuji jsou promene dalsi
zbytecnou jazykovou konstrukci. To se nam to komplikuje.
x := Array new: 20 !:= je prirazeni takze x ted ukazuje na pole. A zase konstrukce hmm....
Array new: 20 !Posleme zpravu instanci ze se ma vytvorit a parametr 20 coz je ucividne velikost. Jeste poznamka ze meneni elementu tedy ceckarsky = neni := to je opravdu jenom spojeni x z objektem. Prvni zprava co ple prijima je at: tedy lisparske elt:
(x at: 1) printNl !vypise prvni prvek. Tedy nil. Prirazovani se dela pomoci zpravy put:
x at: 1 put: 99 !Tohle objektu x tedy poli posle zpravu at z parametrem 1. Ta vytvori novy objekt asociaci. Ktery se uklada do spravneho mista v poli. Zprava put: se yz posila novemu objektu tedy ne uz poli a ten ulozi hodnotu 99. (zpravy co chteji parametry se vetsinou jmenuji z dvojteckou) A muzete si to vypsat jestli chcete a opravdu tam bude 99. Samozdrejme ze si muzete s retezenim zprav a objektu blbnout jak dlouho chcete:
((x at: 1) + 1) printNl !
Je tu hodne dalsich typu co popisovat nebudu. treba set neboli mnozina
apod. Dulezity je:
x := Dictionary new.
x at: 'One' put: 1 !
x at: 'Two' put: 2 !
x at: 1 put: 'One' !
x at: 2 put: 'Two' !
To tam nahaze 4 asociace. Jak vidite at je tu rozsireno a bere vsechny
objekty. Silne pripomina gethash. A uz se muzem na asociace odkazovat:
(x at: 1) printNl ! => 'One' (x at: 'Two') printNl ! => 2 a vypsat vsechno : x printNl ! => Dictionary (1,'One' 2,'Two' 'One',1 'Two',2 )
Smalltalk at: #x put: 0 !
Je vlastne pridani polozky x do slovniku a nastaveni na 0. To je opravdu
elegantni reseni. Asi se divite proc ten #. je to stejny rozdil jako v
lispu mezi "x" a 'x. Jedno je string druhe je symbol. Takze si muzete
vytisknout vsechny promene pomoci:
Smalltalk printNl !Tady ty promene rusit apod. Proste je to moc pekne a moc se mi to libi. Je to jedno misto kde trumfli lisp.
Object
Autoload
Behavior
ClassDescription
Class
Metaclass
BlockContext
Boolean
False
True
CFunctionDescriptor
CObject
Collection
Bag
MappedCollection
SequenceableCollection
ArrayedCollection
Array
ByteArray
CompiledMethod
String
Symbol
Interval
LinkedList
Semaphore
OrderedCollection
SortedCollection
Set
Dictionary
IdentityDictionary
SystemDictionary
Delay
FileSegment
Link
Process
SymLink
Magnitude
Character
Date
LookupKey
Association
Number
Float
Integer
Time
Memory
ByteMemory
WordMemory
Message
MethodContext
MethodInfo
ProcessorScheduler
SharedQueue
Stream
PositionableStream
ReadStream
WriteStream
ReadWriteStream
FileStream
Random
TokenStream
UndefinedObject
Je to celkem zajimavy si ji projit. Clovek obcas zjisti zajimavy veci.
Je to opravdu pekna struktura. Treba ze slovnik je pod mnozinou protoze
slovnik je vlastne mnozina rozsirena o asociace. Nebo ze i trida je
normalni objekt takze kdyz jsme posilaly zpravu tride Array posilali
jsme zpravu normalnimu objektu typu class. To je prece krasne.
Jesne jedna poznamka. Ukladani smalltalkove session je:
Smalltalk snapshot: 'myimage.img' !
A nacteni:
$ mst -I myimage.img
Object subclass: #Account
instanceVariableNames: 'balance'
classVariableNames: ''
poolDictionaries: ''
category: nil !
Trochu to pripomina lispovsky zpusob zakladani.. Je to trochu dlouhy.
Nejlepsi je nastavit si editor aby se to vyprasko po jedny klavese.
Posilame zpravu tride Objekt coz je nejnizsi tride ve strome. Rikame ji
ze se bude jmenovat Account a ze bude mit jednu privatni promenou jmenem
"balance".
Dokumentovani tridy
Kazda trida ma misto na dokumentaci(stejne jako vetsinou v lispovi)
jednoduse ji poslem zpravu:
Account comment: 'I represent a place to deposit and withdraw
money' !
A kdyz ji chceme precist:
(Account comment) printNl !
nebo
(Integer comment) printNl !
!Account class methodsFor: 'instance creation'!
new
| r |
r := super new.
r init.
^r
!!
To se nam ta gramatika mota...jeje
No ten prvni vykricnik to nic..ten jenom odpali vsechno predtim.
Account class posle zpravu Accountu a rika ze chce mluvit primo ze
tridou. Vrati se tedy opravdova trida typu trida a ty se posle zprava
methodsFor: z parametrem 'instance creation' a odpali se to pomoci !.
Zatim to jde. 'instance creation' je komentar. Ale metoda methodsfor:
udla zvlastni vec:prepne interpretr do jineho rezimu!!!To je ale
prasarna co?-my chceme defmethod! Dalsi zbytecna jazykova konstrukce je
hned dalsi radka tedy new. Jasne vytvari novou metodu. Ale proc to neni
delane rozumne? a dalsi je zase nestandard! | r | vytvori lokalni
promenout. Zacina prituhavat...my chceme let! Pomoci dalsi zbytecne
konstrukce ji priradime vysledek formy supper new co posle new svemu
rodicovy tedy tride Objekt co vytvori novy objekt. A hruza dlasi je
tecka! dalsi nestandard. To je oddelovac. Neco jako v cecku strednik
ale je to pojaty spis pascalisticky! Proste ne ukoncovac ale oddelovac
takze jako : v Basicu! A aby toho nebylo malo hned dalsi je ^r coz je
return a hned dalsi jazykova konstrukce(zacina se mi delat spatne) a aby
nas dorazili jeste tam dali !! co prepne interpretr zpatky a zaroven
ukonci definici metody-jenom ukonceni se dela pomoci !
Uff...to byl ale zahul..to jsem se zapotil...Az sem smalltalk vypadal dobre ale tohle.. to je proste paskvil. Zlatej lisp. No ale dalo by se to prezit a tak dal. Ted kdyz zadame:
Account new !Tedy vyvolame construktora objektu. Smalltalk najde nasi metodu a pouzije ji. | r | vytvori lokalni promenou zavola construktor objektu a priradi ho r. Potom zavola metodu init uz od vytvorene instance. To je proto ze new metoda nemuze pristupovat do vytvoreneho objektu takze musi zavolat construktor oz od objektu tedy init co provede inicializaci.
!Account methodsFor: 'instance initialization'!
init
balance := 0
!!
Vidite ze zmizelo volani class. Proto se metoda vytvari rovnou pro
Account a ne pro objekt co ho ridi tedy !account class. Tahle metoda uz
muze pristupovat na interni promene-je v objektu a je to normalni
construktor. Tohle rozdeleni prace mezi dva konstruktory se mi zda
elegantni. Takze init se da posilat uz vytvorenym objektum a new se
posila tride abychom je vytvorily. Jak jednoduche. Metoda nic nevraci
a to neznamena ze vraci neco jako void nebo 0 ale vraci hodnotu sveho
objektu. Aby se metody daly pekne retezit
Account new init init init init !
Posle mockrat init a porad tomu samymu objektu.
Smalltalk at: #a put: (Account new) !
a mame ji v a :) Kdyz ho chceme vytisknout napiseme:
a printNl !
Ale on nas vypece a napise:
an Account
A nevime nic :) Proste jsme neudelali rozumnou metodu na tisk. PrintNl
vola Print co uz nedela newline a ten vola PrintOn z parametrem stdout
tedy trochu jako muj navrh jestli si ho pamatujete. Staci napsat PrintOn
a mame vyhrano:
!Account methodsFor: 'printing'!
printOn: stream
super printOn: stream.
' with balance: ' printOn: stream.
balance printOn: stream
!!
a otestovat:
a printNl !
a tiskne:
an Account with balance: 0
rozbor.. super printOn vypise uz zname an Account. Dalsi radka vypise
normalni text..to uz tu bylo mockrat A dalsi balanci zase nic novyho.
Prace z penezmi
No a ted trochu prace z accountem abychom nebyly porad svorc:
!Account methodsFor: 'moving money'!
spend: amount
balance := balance - amount
!
deposit: amount
balance := balance + amount
!!
No a uz si to muzete zkusit:
a deposit: 125!
a deposit: 20!
a printNl!
a spend: 10!
a printNl!
Podtridy
V manualu jeste delaji podtridu savings co pridava jeste jednu promenou:
Account subclass: #Savings
instanceVariableNames: 'interest'
classVariableNames: ''
poolDictionaries: ''
category: nil !
Vytvoreni podtridy...uz jsem vysvetlil
!Savings methodsFor: 'initialization'!
init
interest := 0.
^ super init
!!
inicializace...
a prace z daty:
!Savings methodsFor: 'interest'!
interest: amount
interest := interest + amount.
self deposit: amount
!
clearInterest
| oldinterest |
oldinterest := interest.
interest := 0.
^oldinterest
!!
Furt nic novyho snad mimo self coz je obdoba this takze vola svoji
vlastni metodu. Tohle jsem projel rychle..doufam ze tomu rozumite..je to
dulezity pro:
Account subclass: #Checking
instanceVariableNames: 'checknum checksleft'
classVariableNames: ''
poolDictionaries: ''
category: nil !
!Checking methodsFor: 'Initialization'!
init
checksleft := 0.
^super init
!!
!Checking methodsFor: 'spending'!
newChecks: number count: checkcount
checknum := number.
checksleft := checkcount
!
writeCheck: amount
| num |
num := checknum.
checknum := checknum + 1.
checksleft := checksleft - 1.
self spend: amount.
^ num
!!
Tady si muzeme pomoci number nastavit cislo a tady vydite jak se daji
zpravi celkem pekne retezit. Zprava se pousti:
Check newChecks: 10 count: 100Vypada to jako volani dvou zprav ale neni...celkem pekne ale mota to syntaxi Writecheck je clekem primitivni funkce co zveci cislo,ubere pocet checku a odebere castu z konta. A jedeme:
Smalltalk at: #c put: (Checking new) !
c printNl !
c deposit: 250 !
c printNl !
c newChecks: 100 count: 50 !
c printNl !
(c writeCheck: 32) printNl !
c printNl !
!Checking methodsFor: 'spending'!
writeCheck: amount
| num |
(checksleft < 1)
ifTrue: [ ^self error: 'Out of checks' ].
num := checknum.
checknum := checknum + 1.
checksleft := checksleft - 1.
self spend: amount
^ num
!!
To je metoda z detekci podteceni. A toto je podminka:
(checksleft < 1)
ifTrue: [ ^self error: 'Out of checks' ].
Jako prvni se vyhodnocuje (checksleft < 1) vysledkem je boolean
tedy objekt true nebo false. Oba boolieny podporuji metodu ifTrue.
U false nedela nic. U true spusti svuj prvni parametr-tim je:
[ ^self error: 'Out of checks' ] To je normalni objekt jako vsechny jine
ale obsahuje kod a ma metodu ktera ho provadi. Tedy ifTrue u true pouze
veme svuj prvni parametr-ccode block a rekne mu aby se provedl. Naprosto
typivky lispovsky pristup. Pouze je tu treba dalsi zbytecna jazykova
konstrukce - [] protoze nema na kod tak pekny typ jako je lispovsky list.
Funkci tehle podminek si muzete pekne otestovat:
true ifTrue: [ 'Hello, world!' printNl ] !
false ifTrue: [ 'Hello, world!' printNl ] !
true ifFalse: [ 'Hello, world!' printNl ] !
false ifFalse: [ 'Hello, world!' printNl ] !
Proti lispu nic noveho..proste unless no.
Zajimave je zpracovani zpravy error co posle metoda svemu objektu. Tato
zprava jako default odesle stejny error volajicimu apod. Toto spracovani
chyb je celkem pekne.
Account subclass: #Checking
instanceVariableNames: 'checknum checksleft history'
classVariableNames: ''
poolDictionaries: ''
category: nil !
Tady ale vznikne kolize ze starymi instancemi co promenou nemaji. Gnu
smaltalk je necha jak jsou a nove metody na ne uz nebudou fungovat. Jine
smalltalky treba zakazou pridat. Musime take rozsirit init:
!Checking methodsFor: 'initialization'!
init
checksleft := 0.
history := Dictionary new.
^ super init
!!
Jak vidite budeme checky ukladat do slovniku.
A rozsirime writeCheck:
!Checking methodsFor: 'spending'!
writeCheck: amount
| num |
"Sanity check that we have checks left in our checkbook"
(checksleft < 1)
ifTrue: [ ^self error: 'Out of checks' ].
"Make sure we've never used this check number before"
num := checknum.
(history includesKey: num)
ifTrue: [ ^self error: 'Duplicate check number' ].
"Record the check number and amount"
history at: num put: amount.
"Update our next checknumber, checks left, and balance"
checknum := checknum + 1.
checksleft := checksleft - 1.
self spend: amount.
^ num
!!
No jsou tu navic jeste komentare co jsou v lispovi v "" Jinak je
tu pridavani checku:
history at: num put: amount.
a testovani jestli nahodou jsme takovy check uz nemeli:
(history includesKey: num)
ifTrue: [ ^self error: 'Duplicate check number' ].
Oba vyrazy jsou snad jasne. Ted ji jeste printit:
!Checking methodsFor: 'printing'!
printOn: stream
super printOn: stream.
', checks left: ' printOn: stream.
checksleft printOn: stream.
', checks written: ' printOn: stream.
(history size) printOn: stream.
!
check: num
| c |
c := history at: num ifAbsent: [ ^self error:
'No such check #' ].
^c
!!
PrintOn je snad jasne. Proste secte vsechny zaznamy v historii a vypise
kolik checku bylo vypsano. Taky je tu funkce check co bere cislo checku
a vrati kolik. Taky ma osetreni. Snad je vse jasne. A ted je treba
udelat kyzenou smycku na tisteni vsech checku:
!Checking methodsFor: 'printing'!
printChecks
history associationsDo: [:assoc|
(assoc key) print.
' - ' print.
(assoc value) printNl.
]
!!
Je videt ze je tu volani metody associationsDo. Jeji princip je asi
jasny proste zavola neco pro vsechny asociace. Zajimavejsi je code blok
ktery zacina [:assoc| atd... To je blok z jednim parametrem. Takze
nahrazuje castecne lisparsky lambda listy-proste vyzaduje parametr assoc
kam metoda associationsDo ulozi hodnotu prvku typu asociace tedy ma dva
hodnoty-key a value a pomoci (assoc key) print. se vypisuje.
1 to: 20 do: [:x| x printNl ] !
To je asi jasne..vytvori se nejaky objekt interval pomoci poslani to: do
1 a parametru 20 tedy interval 1 az dvacet a ten uz ma svoji metodu do a
bloku predava jeden parametr blok si ho bere jako x a tiskne ho.
Objekt interval muze byt i interval objedno:
1 to: 20 by: 2 do: [:x| x printNl ] !
apod.
samozdrejme ze takovy interval si muzeme ulozit:
Smalltalk at: #i put: (Interval from: 5 to: 10) !
i printNl !
i do: [:x| x printNl] !
A delat dalsi praci. Toto reseni se mi zda hodne elegantni. Ale lisp ho
muze mit taky-neni duvod proc ne. Typ interbal by se dal resit pomoci
CLOSU jako bezny objekt a genericka funkce do by taky fungovala
!Checking methodsFor: 'scanning'!
checksOver: amount do: aBlock
history associationsDo: [:assoc|
((assoc value) > amount)
ifTrue: [aBlock value: (assoc key)]
]
!!
metoda value tedy posila bloku hodnotu. Predavani parametru je ve smalltalku
slabe kvuli omezenim z volitebnym poctem parametru protoze na predani
dvou parametru musime mit zcela jinou metodu:
value: 1.hodnota value: 2.hodnotaa je jasne ze jich nemame nekonecno tak muzeme predat maximalne tri hodnoty. No reknete o co je lispovy reseni prez lambda listy pruznejsi. No doufam ze jinak je metoda jasna. muzete testovat:
Smalltalk at: #mycheck put: (Checking new) !
mycheck deposit: 250 !
mycheck newChecks: 100 count: 40 !
mycheck writeCheck: 10 !
mycheck writeCheck: 52 !
mycheck writeCheck: 15 !
mycheck checksOver: 1 do: [:x| printNl] !
mycheck checksOver: 17 do: [:x| printNl] !
mycheck checksOver: 200 do: [:x| printNl] !
Je samozdrejme hodne cest jak napsat tuhle funkci:
!Checking methodsFor: 'scanning'!
checksOver: amount do: aBlock
| chosen |
chosen := history select: [:amt| amt > amount].
chosen associationsDo: aBlock
!!
select je obdoba lispackyho member. Tenhle kod ale preda aBloku celou
asociaci.
Tento soubor je soucasti rozsahle sbirky skolicek na http://www.ucw.cz/~hubicka/skolicky
Take si muzete prohlidnout jeji puvodni textovou podobu
Nebo mi mailnout na hubicka@ucw.cz
Copyright (C) Jan Hubicka 1995