Tato skolicka navazuje na skolicku o zakladech lispu
(defun printf (format & rest parametry) kod)
(append fprintf stdout format parametry)
Stavba lispovych knihoven
Zacnu prikladem:
Je tu funkce member co projde list a zjisti jestli jeji parametr je
clenem toho listu. To je hodne jednoducha funkce co nestoji za to
psat do ceckarsky knihovny. V lispu ale takova funkce member ma
hodne rozsireni:
(member "neco" *modules* :test #'string-equal)
Projde list v promene *modules* jestli neobsahuje retezec neco a pritom bude pouzivat funkci na porovnavani retezcu. Jak vidite to dela z funkce co by v cecku nestala za rec hrozne sikovnou funkci co umi nahradit hodne smycek ktere citelnosti programu nepridaji. Tad si presdstavte ze by jste v cecku delali ze jedna funkce includi fajly a druha ma za ukol zjistit jestli byl fajl uz zincludenej nebo ne-museli by jste udelat jdenosmerne vazany seznam struktur co by obsahovaly string. Funkce co pridava fajly by musela dynamicky naalokovat misto pro nazev a otestovat chyby naalokovat misto pro strukturu a okestovat chyby a pridat do listu-nastavit ukazatel na dalsi a startovaci ukazatel listu. Funkce na testovani by musela list prochazet a pomoci strcmp porovnavat atd. A vidite v lispu si proste vytvorite prazdny list:
(setq *modules* ())a pomoci pop do nej pridavate jmena
(pop jmeno *modules*)a testujete uz uvedenym memberem. Neni to elegantni a snadne? Taky vidite ze konecnymu programu je jedno jestli dela z listama, vektorama, hasovacima tabulkama nebo jinou sekvenci. Proste programator se muze v pulce rozhodnout pro jiny format(treba kdyby includil tisice fajlu bylo by lepsi udelat hasovaci tabulku) . Taky volani funkce je elegantnejsi nez smycka-muzem to rovnou predat jine funkci a nepotrebujeme zadny docasny promeny. To dela lispovsky programy kratsi,rozsiritelnejsi, odolnejsi vuci chybam(funkce member ma urcite vsechno otestovane narozdil od vasich smycek) a nekdy i rychlejsi (normalni clovek treba nepouzije hasovaci tabulku presto ze by urychlila jenom kvuli tomu ze je prace sni tezka). Protoze se clovek hned na zacatku nemusi ucit jake vsechny parametry member bere,ze funguje na vsechny sekvence je uceni lispu jednodusi nez uceni cecka a pokud je knihovna dobre stavena jde to samo. Psani takovych generickych funkci ukazu na konci tyhle skolicky. Takze si klidne muzete pridat novou sekvenci a vsechno na ni bude behat. Ja osobne jsem to uz vyuzil pri emulaci xetoru-paraelniho datovyho typu.
Takhle nejak by se dalo udelat printf funkce v lispu:
Napred by bylo treba napsat funkce na praci ze streamy a nejaky streamovy typ,funkce pro praci z fajlovymi streamy i streamy co ze sapisujou do stringu-aby se dalo udelat sprintf. Potom napsat generickou funkci print co jako prvni parametr bere co ma vypsat a jako mozny druhy kam-stream jinak si sama dosati stdout a dal nejake ty formatovaci optiony. Jako :hex. Zaklad by byla na psani stringu-ta by byla jednoducha proste zapis do streamu. Potom by se udelaly ostatni co by tu na string volaly-taky celkem jednoduche. A mame tisteni na uplne vsechno-neni to snadne? No a aby to bylo jeste hezci mohlo by se dodelat funkce printf co by brala vic parametru a na kazdy by zavolala print a navic by umoznovala pametry:
(printf 2 :bin "ahoj" "caf") =>10ahojcafA pripadne nejake jako :stream apod. no fantazii se meze nekladou. V zadnem lispu jsem takovou funkci jeste nevidel ale neni problem ji udelat. Zdrejme by bylo ale lepsi nejak lepe vyresit predavani parametru. to :bin se mi zda celkem nesikovny. Ale neni to mnohem elegantnejsi rozsiritelnejsi a jednodusi nez v cecku? (uz jste nekdy cetli zdrojaky od printf,vsprintf apod?) . Tenhle napad samozdrejme chce doresit- jednoduchy zpusob vytvareni generickych funkci, jak rozlisovat nove typy pro nove printy apod. Kdyz se nad tim zamyslite vlastne jsem zcela prirozene dosel k objektum a navrhnul objektovou knihovnu. Genericke funkce jsou metody a typy jsou objekty. Treba integer je objekt co ma svoji metodu print co ho tisne. Tady je videt dalsi sila lispu. Vlastne mimochodem jsem dosel k necemu co autori do jinych jazyku pracne sroubuji.(opravdu jsem to nechtel..byl to jen priklad nebylo to promyslene predem) Udelal jsem nejakou dohodu ze tohle jsou objekty, takhle nejak se pouzivaji a kdyz se to pro nejaky problem zrovna hodi je dobre jich pouzit (jak to bylo nadefinovano je popsano na konci skolicky). Pravdepodobne takhle nejak byly objekty vynalezeny, protoze byly jako prvni udelany prave pro lisp prave pro nejakou takovou knihovnu. Proste si toho nekdo vsiml a pojmenoval to. Takhle nejak mimochodem bylo v lispu objeveno uz vic veci. Ja si myslim ze i samotny lisp. Tehdejsi autori v 50tych letech co se motali kolem velkych masin z 256 kilama pameti a delali umelou inteligneci si pravdepodobne jenom chcteli usnadnit praci a tak jazyk udelali co nejmensi. Podle me nemohli domyslet co to vlastne delaji. Vzdyt lisp zene vyvoj dopredu uz 50 let! Samozdrejme ze cecko ma io streamy co jsou velmi podobne moji predstave printu. Ale v cecku je to obrovska a obludna konstrukce plna ukazatelu, pretezovani operatoru a virtualnich metod. To v lispu ne. Tam je to uplne prirozene. (pri pohledu printf vas objekty nenapadnou ale kdyz prepisujete printf do lispu tak jo :) Je tu videt jak zase cecko pomoci ruznych objektovych knihoven dohani to co lisp uz dlouho ma (nejmene od roku 1984-vsechno co o lispu znam bylo popsano v knize co tehdy vysla) Jen tak pro orientaci:
(setq nums '(1 3 2 6 5 4 0)) => (1 3 2 6 5 4 0) (sort nums '<) => (0 1 2 3 4 5 6) nums => (0 1 2 3 4 5 6)a cecko:
#include < stdio.h> #include < stdlib.h> int nums[] = {1, 3, 2, 6, 5, 4, 0}; int compar (const void *a, const void *b) { return (*(int *) a - *(int *) b); } void main (void) { int i; qsort (nums, sizeof (nums) / sizeof (int), sizeof (int), compar); for (i = 0; i < sizeof (nums) / sizeof (int); i++) printf (" %i " , nums[i]); printf (" \n" ); }neni to prece jenom v lispu obratnejsi? Dal popisu nejake pekne konstrukce na ktere jsem zatim narazil.
((ahoj . hallo) (pocitac . computer))Jsou to listy consu ale jejich prohledavani je pomale. To resi hasovaci tabulky. Ty maji vyhodu vtom ze vyhledani trva logN tedy pro 1024 prvku se provede 10 porovnani. To se hodi treba u promenych kde je rychlost pristupu do promeny dulezita. Je to priklad jak jsou lispovske knihovny sikovne na praci z daty. Reknete je to celkem casty problem a v cecku si to clovek musi delat sam. Dal uz jen rychle vytvoreni tabulky:
(make-hash-table)Ta bere hodne parametru nejdulezitejsi je vyhledavaci funkce. Ta musi byt znama predem protoze podle ni se data optimalne rozlozi do tabulky aby to potom slo rychle. Default je eql ale pomoci :test se to da zmenit. Dalsi funkce je:
gethash KEY TABLE &optional DEFAULTco projde tabulku a najde klic. Kdyz tam neni vrati nil nebo default. Pokud nejaky klic chcete do tabulky pridat, nebo prepsat udelate to jednoduse pomoci setf:
(setf (gethash klic table) hodnota)A mate tam hodnotu. Taky se to da provozovat pomoci sethash. Dalsi funkce jsou remhash na mazani,clrhash na cisteni a moc dalsich. Samozdrejme ze funguji vsechny bezne funkce na sekvence. A jsou tu take funkce co fungujou jak z asociovanyma listama tak z hasovacima tabulkama. Proste se muzete rozhodnou jestli setrit pamet-pak listy nebo cas-pak hash.
a takhle je autoload udelany v common lispu:
tech , a ` si nevsimejte ty urcuji postup expandovani makra-co
expandovat a co ne ` funguje stejne jako ' ale bere jako parametr list a
pomoci , se da zrusit ucinek na vybrany parametry
(defmacro defautoload (name module) `(defun ,name (&rest argument-list);vytvorime tu funkci (autoloader ',name ,module argument-list))) ;telo vytvarene ;funkce-zavolani autoloader (defun autoloader (name module argument-list) ; tohle se zavola pri ;prvnim pouziti (unless (member module *modules* :test #'string-equal) ;uz byl modul nahran-je module ;clenem posloupnosti modules ;pritom pouzivej testovaci ;funkci co porovnava stringy ;v cecku by to byla celkem ;slusna smycka :) (fmakunbound name) (load (merge-pathnames module si::*system-directory*))) ;nebyl-nahrajeme (apply name argument-list));a zavolame-presne jak jsem rikalsamozdrejme ze k autoload je tam toho vic-def autoload makro a spol. ale v zasade to je prene to co jsem rikal
Strukturovane typy
Asi vam v lispovi chybi cokoliv co pripomina ceckarsky struktury.
Struktura v cecku je typ co obsahuje nekolik promenych ruznyho typu na
ktere se odkazuje nazvy. Protoze v lispovi promena muze byt libovolneho
typu je struktura uz jenom vec o n promenych na ktere se odkazuje nazvy.
Vec o n promenych muze byt treba list nebo pole takze jediny problem je
udelat nejaky pekny interface na praci se cleny listu pomoci jejich jmen.
Ktomu slouzi makro defstruct. To se vola:
(defstructtedy:jmeno-prvni-promene jmeno-druhe-promene atd)
(defstruct blb jedna dva tri)Tato funkce udela jenom to ze vytvori nekolik dalsich funkci. Zakladni je make_blb co vytvori strukturu blb tedy ten list(nebo casteji pole). Takhle to pise common lisp co na to ma samozdrejme specialni format tisku:
> (make-blb) #s(BLB :JEDNA NIL :DVA NIL :TRI NIL)Samozdrejme ze muze byt rozsirena treba pomoci &key:
> (make-blb :jedna 1 ) #s(BLB :JEDNA 1 :DVA NIL :TRI NIL)No a pak udela funkce na vybrani urciteho prvku takze kdyz v promene blb mame strukturu blb muzeme udelat:
> (blb-jedna blb);prohlidnout si hodnotu NIL a jak jsem psal v predchozi skolicce: > (setf (blb-jedna blb) 2);nastavit hodnotu 2 > (blb-jedna blb);a uz je zmenena 2A to uz je sice nezvykla ale celkem uchazejici prace ze strukturama. Syntaxe je sice divna(ja dam prednost blb.jedna pred (blb-jedna blb) ale naprosto funkci a plnohodnotne struktury to jsou a zase bez zasahu do interpretru. A zase rozsiritelne. Treba defstruct vytvari funkce typu blb-copy na kopirovani apod. V common lispu je defstruct tak rozsirene ze toto makro je napsano v 30 kilovem zdrojaku
Kontextova napoveda
Dulezita soucast knihovny i programu je dokumentace :) nee
nekamenovat..ja ji taky nepisu ale jak takovou dokumentaci resi lisp? No
samozdrejme ze fikane a kazda implementace po svem. Treba emacs ji prida
do lambda listu-tedy kazda funkce ma misto na documentation string a pak
staci udelat nejakou funkci help co ten string zobrazi a je vyhrano.
Dokumentace se pise pekne ke kazde funkci jenom misto komentaru jsou tam
uvozovky. Jine lispy to delaji jinak tohle je jenom priklad. Zase zadny
brutalni zasahy pouze rozsirime funcall o podminku ze kdyz po symbolu
lambda je string je to dokumentace a tudiz se zpracovani posune. To
same jde u promenych apod takze takove defvar muze vypadat takto:
(defvar vc-default-back-end nil "*Back-end actually used by this interface; may be SCCS or RCS.The value is only computed when needed to avoid an expensive search.") a clovek vi na cem je.
Debuger
Samozdrejme ze i tohle jde snadno vytvorit v lispu. Protoze jednotlive
casti interpretru se daji volat oddelene a mezitim delat cokoliv z
promenymi programu apod. Je to vlastne sranda. A vetsina interpretru
take debugger ma ale ovlada se pokazde jinak.
Oobjekty-CLOS
I tak slozita vec jako objekty jde v lispu snadno. A lip nez v cecku :)
(ukazu jen zaklady a budu se drzet zase tutorialu dodavanym z clispem)
Definovani trid:
pomoci defclass(defclass je stejne jako defstruct napsana v lispu):
(DEFCLASS class-name (superclass-name*) (slot-description*) class-option* )class options zapomeneme. zatim. slot options jsou udelany tak ze tam jsou keywordy(tedy slova od : ktera clisp nevyhodnocuje) a za kazdym je nejaka forma nejpouzivanejsi jsou:
:ACCESSOR function-name :INITFORM expression :INITARG symbolprotoze objektry jsou vlastne struktory defclass se da srovnat z defstructem. Jsou tu ale male rozdily :) Priklad defstructu:
(defstruct person (name 'bill) (age 10) )Tady je ukazka vytvareni struktury o dvou prvcich a automatickyho nastaveni default hodnoty. Pri vytvareni to samozdrejme muzem zmenit:
(make-person :name 'george :age 12)Abychom udelali podobnou tridu musime:
(defclass person () ((name :accessor person-name :initform 'bill :initarg :name ) (age :accessor person-age :initform 10 :initarg :age ) ))jak vidite je tu specifikovane jmeno funkce ne pristup k objektu, jmeno keywordy na meneni pri inicializaci a default hodnota. Je to opravdu jen rozsirena struktura.
Instance
Na vytvoreni instance slouzi funkce:
(MAKE-INSTANCE class {initarg value}*)Ta funguje jako funkce na vytvareni struktury:
(make-instance 'person :age 100)Takhle nastavi stari na 100, a jmeno na billa(chudak). Je dobrym zvykem delat u trid konstruktory. To ale make-instance nepovoluje. No ale my se muzem dohodnout ze konstruktor se bude vzdycky jmenovat make-
(defun make-person (name age) (make-instance 'person :name name :age age) )To uz pripomina objekty...A uz muzeme pracovat z objektem:
[cl] (setq p1 (make-instance 'person :name 'jill :age 100)) ;vytvorime #< person @ #x7bf826> ;zase jiny format tisteni..v clispu bezne [cl] (person-name p1);chceme jmeno jill [cl] (person-age p1);chceme stari 100 [cl] (setf (person-age p1) 101);zmenime jmeno 101 [cl] (person-age p1);zmenime stari 101muzeme na to lest i pomoci slot-value jako u struktur:
[cl] (slot-value p1 'name) jilla describe nam rekne co vi..
[cl] (describe p1) #< person @ #x7bf826> is an instance of class #< clos:standard-class person @ #x7ad8ae> : The following slots have :INSTANCE allocation: age 101 name jillian
Dedicnost
Priklad nema co dedit-ve volbe superclass mel ()-to je to same jako nil
tedy nic. Pokud neco dedime muze se stat ze sloty se muzou tlouct.
Parametry urcene novou tridou musou bud prepsat stare nebo udelat
sjednoceni:
:ACCESSOR -- sjednoceni :INITARG -- sjednoceni :INITFORM -- prepisepriklady dedicnosti:
[cl] (defclass teacher (person) ((subject :accessor teacher-subject :initarg :subject ) )) #< clos:standard-class teacher @ #x7cf796> [cl] (defclass maths-teacher (teacher) ((subject :initform "Mathematics")) ) #< clos:standard-class maths-teacher @ #x7d94be> [cl] (setq p2 (make-instance 'maths-teacher :name 'john :age 34 ) ) #< maths-teacher @ #x7dcc66> [cl] (describe p2) #< maths-teacher @ #x7dcc66> is an instance of class #< clos:standard-class maths-teacher @ #x7d94be> : The following slots have :INSTANCE allocation: age 34 name john subject "Mathematics"(svanda co?)
(defclass a (b c) ...)Jediny problem je jak se parametry budou prepisovat. Nejvetsi prioritu ma a-nejvic specificka a potom plati pravidlo ze drive listova jsou vice specificke.
Genericke funkce a metody
Genericke funkce v lispu jsou podobne spravam co si objekty posilaji ale
misto:
(SEND instance operation-name arg*)se pise
(operation-name instance arg*)Tedy genericka funkce je funkce co si zjisti jaky objekt je prvni parametr a podle toho zavola spravnou metodu.
(DEFGENERIC jmeno lambda-list) - lambda-list je v lispu list co obsahuje prametry funkce. muze byt pouzite pro definovani takove funkce. Ale nemusi se to delat protoze defmetod ji zavola automaticky. Ale nekdy je dobre rict pomoci defgeneric ze takova funkce existuje a jake ma prarametry.
Definovani metody se dela:
(DEFMETHOD generic-function-name specialized-lambda-list forms )Je to vpodstate stejne jako defun:
(DEFUN function-name lambda-list form*)Jenom lambda-list je rozsiren o specialni moznost. Muzete rice ze tento parametr musi byt instance tridy ta a ta. To se dela tak, ze misto parametru se napise:
((var1 class1) (var2 class2) ...)priklad:
(defmethod change-subject ((teach teacher) new-subject) (setf (teacher-subject teach) new-subject) )Pokud chcete treba nejak omezit new-subject udela se to nejak takto:
(defmethod change-subject ((teach teacher) (new-subject string)) (setf (teacher-subject teach) new-subject) )To je vyhoda oproti klasickym objektovym jazykum ktere specializuji vlastne jenom prvni parametr. Da se tim delat treba plus z prevodama tim ze reknete jakeho typu musi parametry byt. Priklad:
(defmethod test ((x number) (y number)) '(num num) ) (defmethod test ((i integer) (y number)) '(int num) ) (defmethod test ((x number) (j integer)) '(num int) ) (test 1 1) => (int num), not (num int) (test 1 1/2) => (int num) (test 1/2 1) => (num int) (test 1/2 1/2) => (num num)
Kombinovani metod ktere provozuje lisp jako default je standartni kombinovani. Muzete si ale udelat i vlastni. Standartni kombinovani ma tyto pravidla:
(defclass food () ()) (defmethod cook :before ((f food)) (print "A food is about to be cooked.") ) (defmethod cook :after ((f food)) (print "A food has been cooked.") ) (defclass pie (food) ((filling :accessor pie-filling :initarg :filling :initform 'apple)) ) (defmethod cook ((p pie)) (print "Cooking a pie") (setf (pie-filling p) (list 'cooked (pie-filling p))) ) (defmethod cook :before ((p pie)) (print "A pie is about to be cooked.") ) (defmethod cook :after ((p pie)) (print "A pie has been cooked.") ) (setq pie-1 (make-instance 'pie :filling 'apple))A odpalit:
[cl] (cook pie-1) "A pie is about to be cooked." "A food is about to be cooked." "Cooking a pie" "A food has been cooked." "A pie has been cooked." (cooked apple)Toto pojeti objektu je sice trochu divoke na syntaxi ale ma toho hodne co cecko nema. Proto bylo vytvoreno objektive c ktere ma toho hodne z tehle objektu a je lepsi nez ++. Az se ho trochu naucim tak napisu. No to je asi vsechno. Doufam ze to alespon nekoho zajimalo.
V cem se lispovske objekty lisi?
No to je opravdu tezky vysvetlit. Hlavne proto ze objektum moc
neholduju. Ale C++ ma objekty ktere se schoduji z pojetim objektu
poprve odzkousenych v simlue 67 tedy tzv. simule 67 school of OO
programing. Lispovske objekty byly vyvynuty puvodne pro lisp a pozdeji
pro smalltalk takze se tomu rika smalltalska skola OO programovani. Je
tu jeden hlavni rozdil. V C++ je staticky typ objektu ktery rika jake
zpravy muzete poslat. A nejde je uz nijak menit. Narozdil od lispu kde
si tyto zpravy muzete pridavat dynamicky. ++ pojeti je bezpecnejsi pro
kompilaci protoze se predem odhali chyby. Lisp pojeti je mocnejsi a
flexibilnejsi.
Na druhou stranu lidi c ++ rikaji ze dobre navrhnuty program nic takoveho nepotrebuje. A dokonce v ansi C++ je na to konstrukce ale rozhodne lisp to ma mnohem prirozenejsi.
(ceckari nebrecte, ja mam taky cecko radsi. Je prece rychlejsi zere min pameti novejsi a ja nevim co jeste)
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