Zajimave konstrukce v lispu


Tato skolicka navazuje na skolicku o zakladech lispu

Obsah:


Uvod

Tady bych rad ukazal jake zajimave konstrukce figly a dalsi fikane veci co lisp umoznuje. Hlavne bych tu rad ukzal veci jako struktury,objekty apod co normalne jazyky musi mit v sobe a lisp ne jenom kvuli tomu ze se na programovani kouka trosicku jinak. To dokazuje kvalitu symetricnost rozsiritelnost a univerzalnost tohodle jazyka proto si myslim ze to stoji za precteni.(poznamka po dopsani:opravdu to stoji za to sam jsem se dovil co jsem tam vsechno napsal a udelal jsem si jasno v hodne vecech)

Ceckarske knihovny X lisparske

Popisu tady zpusob jakym se v lispu pisou knihovny a co vsechno v nich jde udelat. Jako cecko ma mnohem vice moznosti pro psani knihoven nez pascal-promeny pocet parametru,makra,hacka kde clovek muze delat ruzne triky a proto ma knihovny mnohem lepsi univerzalnejsi a rozsahlejsi nez pascal ma lisp nesrovnatelne vic moznosti pro psani knihoven nez cecko. Proto jsou lispovske knihovny jeste vetsi univerzalnejsi rozsahlejsi a casteji pouzivane nez v cecku. Ja je bohuzel moc neumim-za dva tydny se to neda zvladnout ani na to nemam manual. Takovou lisparsky pojatou funkci v cecku je printf. Printf neni v cecku jedna funkce. Je to cela skupina jejiz zakladem je vsprintf pro zapis do stringu. Vsechny ostatni funkce ji volaji jenom ze vstupem a vystupem neco udelaji-vprintf ji rozsiruje o zapis do streamu, printf ji pekne predkouse parametry do va_listu a nastavi stream na stdout. Je tu take register_printf_function co umoznuje pridat novy vystupni format-treba %W co bude tisknout vidgety apod. Proste je to velmi zajimavy rozsiritelny a vykony nastroj. Pomoci printf funkci toho udelate opravdu hodne. A je to jedna z veci co dela z cecka jazyk lepsi nez je pascal.(co jsem se to do pascalistu nahucel) Ale ted z druhe strany. Podivejte se kolik tato konstrukce prinasi problemu:

  1. V cecku neni mozne zjistit pocet parametru funkce-je nutne ktomu pouzivat dost zmotany formatovaci retezec co celou vec dost komplikuje a blbe se uci.
  2. Neni mozne zjistit jakeho typu jsou parametry a pokud maji ruznou delku na nekterych architekturach neni mozne ani poznat kde nejaky parametr zacina a kde konci. Musime proste verit ze formatovaci retezec je spravne. Pokud ne buh nas chran abychom neslapli do nejake zakazane pameti a nevyvolali segfault nebo jeste neco horsiho. Neni dokonce ani moznost zjistit jestli je treba tenhle kus pameti brany jako ukazatel spravne tedy jestli ukazuje na neco rozumneho.
  3. Neni mozne zavolat funkci z promenym poctem parametru stejne jako byla zavolana funkce volajici teda printf nemuze volat fprintf(stdout,parametry co dostala) a musi volat vprintf co uz bere parametry predkousane. Taky treba nemuzeme pomoci jdenoho printf vytisknout n clenu pole kde n je promena proste tim ze bychom si udelali formatovaci retezec a pak tech prvnich n clenu nejak printfu predali
  4. Cecko nema zadny dobry system na rizeni chyb vzniklych ve funkcich vetsina funkci proste vypise chybove hlaseni-hlavne matematicke nebo vyvola nejaky signal-treba free nebo malloc ale vetsinou se zneho moc nedozvite a neni zadny centralni mechanizmus na osetrovani takovych chyb. Vetsina funkci se ani moc nehlida a mlcky se predpoklada ze je vsechno vporadku.
  5. Cecko nema moc dobrych zpusobu na predavani nejakych pridavnych parametru jako jsou treba parametry v shellu(myslim treba ls -l) Musi se to delat but tak ze se parametr predava vzdycky a nebo predavat nejakou promenou. Jinak by fprintf bylo zbytecne proste bychom printf dali prepinac -output
  6. Jsou tu problemy z nestandanrdnosti alokace pameti v cecku-muze se pouzivat malloc ale take to muze byt normalni pole nebo momoci alloca v zasobniku. Proto treba vsprintf nemuze prodlouzit retezec kdyz tistena data se tam uz nevejdou. Dokonce ani nema sanci zjistit jak velke pole je. Proste musi verit tomu ze pole je dost velke a ze neslapne vedle. To ale nemusi byt pravda.
  7. Neni tu zadny standard na rozsirovani tekovychto konstrukci - je tu treba "sita na miru" funkce register_printf_function
  8. Vsechny ostatni problemy na ktere jsem zapomel
To pouzivani a rozsirovani printfu a ostatnich funkci dost komplikuje a tak ho clovek ze zacatku nema rad pak se snim zjije a poradne ho pochopi az kdyz si precte zdrojaky. Taky pouziti printf neni uplne bezpecne a muze zpusobit ruzne preteceni a vypadky. Ma zbytecne omezeni. Je tezke se ho naucit. Proto takovych sikovnych konstrukci v ceckarskych knihovnach je malo (o moc dalsich nevim - neco podobneho najdete az v objektovych knihovnach na uzivatelske interface,iostreamy apod.) a cecko propaguje spis jednoduche jednoucelove a kratke funkce. Jak to resi lisp? Tedy reseni na problemy:
  1. Pocet parametru: Parametry funkce dostane v listu-v parametrech date & rest x a x je potom list co obsahuje vsechny parametry funkce co nebyly predany jinak tedy printf by vypadal nejak takhle:
    (defun printf (format & rest parametry) kod)
    spocitat pocet prvku listu je jednoduche - jde to udelat pomoci smycky a samozdrejme ze je pro to funkce length zminena v predchozi skolicce
  2. typy: Kazdy obhekt vi svuj typ. Takze si to muzete o jednotlivych parametrech zjistit a bylo by to zahodno to udelat a vybombit chyby
  3. Volani z promenym poctem parametru: Protoze si muzu do nejake promene vytvorit list z jejim volanim a potom tento list nechat vyhodnotit a tim ji zavolat to samozdrejme mozne je. Take ktomu slouzi append. treba kod printf by byl:
    (append fprintf stdout format parametry)
    Append bere posledni parametr jako list a prida ho k ostatnim parametru.
  4. Chyby:
    Rizeni chyb jde v lispu udelat moc zpusobama-treba ceckarsky udelat nejakou funkci errno a vracet string z chybou a pritom errno nastavit. nebo udelat nejaky zberac chyb jako jsou signaly proste funkci co se zavola pri vzniku chyby a moc dalsich veci. Taky otestovani chyb v lispu jde udelat lepe protoze si funkce muze testovat pocet a typy svych parametru.
  5. Pridavny parametry:
    Protoze volitebny pocet parametru je v lispu sranda jde udelat funkce z promenym poctem parametru kde se bezne pouzivaji jen n prvnich a dalsi pouzit prave pro predavani optionu. Protoze slova od : jsou konstanty jde predavani delat treba (jmenofunkce normalparametry :test testovacifunkce) Takze treba funkce OPEN bere jeden parametr se jmenem a potom parametry jako :io :append :overwrite apod. A hned je pouzivani jednodusi. A kdyz clovek od funkce potrebuje neco vic-otevirat pro zapis proste se jukne do manualu a ono to tam vetsinou je. Clisp ma na to dokonce rozsireni v definici funkce-& key jestli si pamatujete od minule skolicky. Takze tenhle styl predavani jde celmi jednoduse.
  6. O allokaci pameti se staraji objekty-proste objekt vytvorite pamet se mu automaticky prideli a pridava nebo ubira se jak je treba stara se o to vlastni interpretr zcela dynamicky. Tenhle problem v lispu proste nenastane.
  7. To neni chyba jazyka ale spis navrhu knihoven. V common lispu se ten standard jmenuje CLOS
No jak je videt srovnani dopadlo pro cecko moc spatne :( A to jsem delal jenom printf. Predstavte si kdyby printf se volalo normalne jenom z listingem parametru pro tisteni a vsechno ostatni-tisk do souboru,zmena formatu,formatovaci string,tisteni do stringu by se dalo delat pomoci prepinacu :output :format nebo treba :hex :bin apod. Hned by to bylo veselejsi. Clovek by nejprve zjistil ze se tiskne pomoci print(promena) a teprve casem by to rozsiril a pascalisti by nervali: my chceme write! A to je prave sila lispu! Proste na vsechno je nejaka ta sikovna funkce co to umi nebo pripadne nejaky ten option. Umeni psat v lispu je pouzivat tyhle sikovne knihovny. Ceckarska knihovna je rozsahla ale nema treba vybec zadne sikovne funkce na praci z listy nebo jinak strukturovanymi daty(jedina me znama funkce na data je qsort). To je prave to co lisp ma.

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:
  1. Funkce member nemusi pracovat z listama ale treba z polema. To znamena ze je genericka-sama si zjisti typ co dostala a podle toho se zachova. Je to typicky priklad funkce co pracuje ze sekvencema. To je typ co ma nekolik casti a na kazdou tu cast se da pristupovat pomoci genericke funkce elt coz je funkce co vrati n. prvek sekvence. Pomoci ni a jeste dalsi genericke funkce length co zjisti delku sekvence uz member jde jednoduse napsat a bude fungovat taky na vsechny sekvence-samozdrejme ze funkce member ma urcite specialni optimalizovany kod pro listy kde tenhle pristup by zdrzoval.
  2. Co treba kdyz potrebujeme zjistit jestli je v sekvenci nejaky string? No to je vpodstate stejny problem jenom misto eql tedy zjistovani jestly jsou objekty schodne pouzijeme jinou funkci. To by v cecku znamenalo delat novou funkci. Ale v lispu jednoduse pridame parametr :test co umoznuje definovat novou testovaci funkci. Samozdrejme ze takovych optionu ma member vic. Treba test-not apod.
potom treba:

(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")
 =>10ahojcaf
A 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:
1983
prvni xt
1984
Rozsireni sharpa MZ-800 sinclairu a podobnych Z-80 pocitacu Tusim ze taky rok standarizace ansi-c a prvni AT msdos 2.0 z castecnou podporou harddisku V ty dobe se o C++ jeste nikomu nesnilo
(asi si pucim nejakou novou knizku o lispu abych vedel jak se bude programovat v roce 2005 :)

Male porovnani

trideni v lispu:

          (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.

HASOVACI TABULKY

Psal jsem tu o nich a tak jsem si uvedomil ze hodne lidi presne nevi co to je. Hasovaci tabulky tedy mapujou klice do hodnot. Proste fungujou jako slovnik-date ceske slovo tedy klic a vypadne vam ze tam neni. Ne tedy vazne vypadne vam hodnota tedy anglicke slovo a mozna taky to ze tam neni. Tomu se rika asociovane pole. Lisp takove pole dela take pomoci asociovanych listu:
((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 DEFAULT
co 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.

AUTOLOAD

Protoze skoro vsechno jde napsat v lispovi maji bezne interpretry lispu velike knihovny(1-8MB) funkci. Aby tyhle funkce byly pristupne musi se nazacatku nahrat ale zinterpretrovat 8MB zdrojaku uz prece jenom chvili trva a taky by to zralo hrozne moc pameti a bezne lispy si vystaci 1-2MB musi tu byt figl. Je jasne jaky-nahravat funkce az kdyz jsou treba. Vlastne takove owerlaye. To je relativne slozita vec v cecku-udelat dohravani funkce za behu v linuxu znamena zdilenou knihovnu a kdovi co jeste. V lispovi se to dela zcela jednoduse:
(takhle je to reseno v emacs) Je tu makro autoload ktere zavolame se jmenem funkce a jmenem souboru a to vytovri autoload objekt co po zavolani nacte a zinterpretruje soubor. Autoload objekt je v emacs udelan jako list kde prvnim prvkem je autoload. A nahravani je pridano do funkce eval(v nekterych lispech to jde napsat v lispovi ale obecne pridavani typu je velikej problem) Kdybychom ale nechteli rozsirovani evalu muzeme autoload objekt udelat jako normalni funkci z promenym poctem parametru co po svem zavolani vincludi file tim se predefinuje a rekurzivne se zpusti pomoci append. Ani include nemusime davat do interpreteru protoze pokud mame primitivni funkce na praci se soubory a to je nutne protoze to je volani systemu - od toho primitivni funkce jsou muzeme jednoduse soubor nacist a potom prohnat readem a evalem a tim vlastne udelat to co dela bezny interpretr. Takze autoload JDE napsat bez nejmensiho zasahu do interpretru. Samozdrejme ze to vsechno jde rozsirit-treba udelat funkci co projde soubor a sama vytvori autoloady pro vsechny funkce v souboru a moc dalsich vylepseni.

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 rikal
samozdrejme 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:
(defstruct 
    jmeno-prvni-promene
    jmeno-druhe-promene atd)
tedy:
(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
2
A 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 symbol
protoze 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- a ze ho vzdycky udelame. Tedy:
  (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
   101
muzeme na to lest i pomoci slot-value jako u struktur:
   [cl] (slot-value p1 'name)
   jill
a 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  --  prepise

priklady 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?)

Vicenasobna dedicnost

No tak pise se to takhle:

  (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

Kdyz vic trid definuje metodu pro generickou funkci a vic nez jedna metoda je mozna pro dane parametry jsou zkombinovany do jedne efektivni metody. Kazda metoda je jenom cast konecne metody.

Kombinovani metod ktere provozuje lisp jako default je standartni kombinovani. Muzete si ale udelat i vlastni. Standartni kombinovani ma tyto pravidla:

takhle se parametry specifikuji:

   (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