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 UndefinedObjectJe 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 AccountA 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: 0rozbor.. 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