Filozofie a základní pojmy
Co je Common Lisp a proč je unikátní
Common Lisp (CL) je multi-paradigmatický, dynamicky typovaný jazyk se standardem z roku 1994 (ANSI). Je to jeden z nejstarších a nejpropracovanějších programovacích jazyků – podporuje funkcionální, imperativní i objektově orientované programování (CLOS). Má makrosystém, který nemá konkurenci: kód je data a data jsou kód (homoikonicita).
| Pojem | Vysvětlení |
|---|---|
| S-výraz | Základní syntaktická jednotka: atom nebo seznam |
| Atom | Číslo, řetězec, symbol, znak |
| Forma | S-výraz, který lze vyhodnotit |
| Speciální forma | Vestavěná syntaxe (IF, LET, DEFUN...) |
| Makro | Transformace kódu za kompilace |
| REPL | Read-Eval-Print Loop – interaktivní prostředí |
| Package | Jmenný prostor pro symboly |
| GC | Automatická správa paměti |
; V Lispu je VŽDY: (funkce arg1 arg2 ...) ; To je vše – žádná jiná syntaxe neexistuje! (+ 1 2) ; → 3 (sčítání) (* 3 4 5) ; → 60 (násobení více čísel) (+ 1 (* 2 3)) ; → 7 (vnořené výrazy) ; Porovnání s "infix" zápisem: ; Python: 1 + 2 * 3 ; CL: (+ 1 (* 2 3)) — jednoznačné, bez priorit! (print "Ahoj světe!") ; vypíše řetězec
Základy – atomy a vyhodnocování
Symboly, čísla, znaky, booleany, nil a t
| Literál | Typ | Příklad | Poznámka |
|---|---|---|---|
| 42 | INTEGER | 42, -7, 0 | Celé číslo, libovolná velikost (bignum) |
| 3.14 | FLOAT | 3.14, 1.0e10 | Desetinné číslo |
| 1/3 | RATIO | 1/3, 22/7 | Přesný zlomek – unikátní v CL! |
| #C(1 2) | COMPLEX | #C(3.0 -2.0) | Komplexní číslo |
| "text" | STRING | "Ahoj\nsvěte" | Řetězec v uvozovkách |
| #\A | CHARACTER | #\A #\Space #\Newline | Jednotlivý znak |
| symbol | SYMBOL | x, moje-funkce, + | Identifikátor (case-insensitive!) |
| :klíčové | KEYWORD | :name :color :x | Symbol začínající dvojtečkou |
| T | BOOLEAN | T | Pravda (true) |
| NIL | BOOLEAN / NULL | NIL, '() | Nepravda i prázdný seznam |
; Quote (') zabrání vyhodnocení – vrátí symbol/seznam doslova (print 'ahoj) ; → AHOJ (symbol, ne volání funkce) (print '(1 2 3)) ; → (1 2 3) (seznam, ne výpočet) (print (+ 1 2)) ; → 3 (vyhodnotí se) ; Backquote (`) a comma (,) – pro makra a šablony (let ((x 42)) (print `(hodnota je ,x))) ; → (HODNOTA JE 42) ; Typ hodnoty zjistíme funkcí TYPE-OF (type-of 42) ; → (INTEGER 0 4611686018427387903) (type-of "ahoj") ; → (SIMPLE-ARRAY CHARACTER (4)) (type-of 'x) ; → SYMBOL (type-of nil) ; → NULL
Datové typy a predikáty
Testování typů pomocí predikátových funkcí
| Predikát | Testuje | Příklad → výsledek |
|---|---|---|
| numberp x | Je číslo? | (numberp 42) → T |
| integerp x | Je celé číslo? | (integerp 3.0) → NIL |
| floatp x | Je float? | (floatp 3.14) → T |
| rationalp x | Je racionální? | (rationalp 1/3) → T |
| stringp x | Je řetězec? | (stringp "hi") → T |
| symbolp x | Je symbol? | (symbolp 'x) → T |
| listp x | Je seznam? | (listp '(1 2)) → T |
| null x | Je NIL? | (null nil) → T |
| consp x | Je neprázdný pár? | (consp '(1)) → T |
| functionp x | Je funkce? | (functionp #'+) → T |
| characterp x | Je znak? | (characterp #\A) → T |
| arrayp x | Je pole? | (arrayp #(1 2 3)) → T |
| keywordp x | Je klíčové slovo? | (keywordp :name) → T |
| typep x typ | Je x daného typu? | (typep 3 'integer) → T |
Proměnné a vazby
DEFVAR, DEFPARAMETER, DEFCONSTANT, LET, LET*, SETF
Definuje globální dynamickou proměnnou. Inicializuje ji pouze pokud ještě není definována. Konvence: hvězdičky kolem názvu (earmuffs).
(defvar *pocet-uzivatel* 0 "Celkový počet uživatelů systému.") (defvar *databaze* nil "Globální databáze záznamů.") ; DEFVAR neinicializuje znovu při reloadu!
Jako DEFVAR, ale VŽDY nastaví hodnotu – vhodné pro konfigurovatelné parametry.
(defparameter *verze* "1.0.0" "Verze aplikace.") (defparameter *max-pokusu* 3 "Max. počet pokusů pro přihlášení.")
Definuje konstantu – hodnotu nelze měnit za běhu.
(defconstant +pi+ 3.14159265358979) (defconstant +max-velikost+ 1024) ; Konvence: konstanty v +plusech+
Lokální vazby proměnných. LET váže všechny najednou (paralelně), LET* sekvenčně (každá vidí předchozí).
; LET – paralelní vazba (let ((x 10) (y 20) (jmeno "Karel")) (format t "~a má ~a bodů~%" jmeno (+ x y))) ; LET* – sekvenční (každá vidí předchozí) (let* ((a 5) (b (* a 2)) ; b vidí a (c (+ a b))) ; c vidí a i b (print c)) ; → 15
Přiřadí novou hodnotu proměnné nebo místu (generalizované přiřazení). SETF je univerzálnější.
(defvar *skore* 0) (setf *skore* 100) ; nastav proměnnou (print *skore*) ; → 100 ; SETF funguje i na složitá místa: (defvar *v* (list 1 2 3)) (setf (first *v*) 99) ; *v* → (99 2 3) ; INCF / DECF – inkrementace/dekrementace (incf *skore*) ; *skore* = 101 (incf *skore* 5) ; *skore* = 106 (decf *skore* 10) ; *skore* = 96
Funkce
DEFUN, LAMBDA, &optional, &rest, &key, uzávěry, HOF
; Základní funkce – sčítání dvou čísel (defun secti (a b) "Vrátí součet čísel a a b." ; docstring (volitelný) (+ a b)) ; poslední výraz je návratová hodnota (secti 3 4) ; → 7 ; Volitelné parametry – &optional (defun pozdrav (jmeno &optional (titul "pane")) (format nil "Dobrý den, ~a ~a!" titul jmeno)) (pozdrav "Novák") ; → "Dobrý den, pane Novák!" (pozdrav "Nováková" "paní") ; → "Dobrý den, paní Nováková!" ; Zbývající argumenty – &rest (defun secti-vse (&rest cisla) (apply #'+ cisla)) (secti-vse 1 2 3 4 5) ; → 15 ; Klíčové parametry – &key (pojmenované argumenty) (defun vytvor-uzivatel (&key jmeno (vek 0) (role :uzivatel)) (list :jmeno jmeno :vek vek :role role)) (vytvor-uzivatel :jmeno "Karel" :vek 30 :role :admin) ; → (:JMENO "Karel" :VEK 30 :ROLE :ADMIN)
; Lambda – anonymní funkce (funcall (lambda (x) (* x x)) 5) ; → 25 ; Uzávěr – funkce, která si pamatuje prostředí (defun vytvor-scitac (n) (lambda (x) (+ x n))) ; zachytí n z vnějšího prostředí (defvar *pricti-10* (vytvor-scitac 10)) (funcall *pricti-10* 5) ; → 15 (funcall *pricti-10* 100) ; → 110 ; Funkce vyššího řádu – MAPCAR, FUNCALL, APPLY (mapcar #'sqrt '(4 9 16 25)) ; → (2.0 3.0 4.0 5.0) (mapcar (lambda (x) (* x 2)) '(1 2 3 4 5)) ; → (2 4 6 8 10) (remove-if #'oddp '(1 2 3 4 5 6)) ; → (2 4 6) – odstraní lichá čísla (reduce #'+ '(1 2 3 4 5)) ; → 15 – sečte všechny prvky
Podmíněné výrazy
IF, WHEN, UNLESS, COND, CASE, AND, OR
Základní větvení. Pokud je podmínka pravdivá (cokoliv != NIL), provede then-větev, jinak else-větev.
(if (> 5 3) "pět je větší" ; then "tři je větší") ; else ; → "pět je větší" ; Více výrazů v then/else → progn (if (evenp 4) (progn (print "Sudé číslo") (print "OK")))
WHEN: provede tělo pokud je podmínka pravdivá. UNLESS: opak (provede pokud je NEPRAVDIVÁ). Obě implicitně wrappují tělo v PROGN.
(when (> *skore* 100) (print "Rekord!") (incf *level*)) (unless (null *databaze*) (print "Databáze má data"))
Vícehodnotové větvení – jako elsif řetězec. Každá klauzule je (podmínka výraz...). T jako podmínka = else.
(defun klasifikace (body) (cond ((>= body 90) "Výborně") ((>= body 75) "Chvalitebně") ((>= body 60) "Dobře") ((>= body 45) "Dostatečně") (t "Nedostatečně"))) (klasifikace 82) ; → "Chvalitebně"
Porovnává hodnotu se seznamem klíčů pomocí EQL. OTHERWISE nebo T = výchozí případ.
(defun den-v-tydnu (n) (case n (1 "Pondělí") (2 "Úterý") (3 "Středa") ((4 5) "Čtvrtek nebo pátek") ((6 7) "Víkend") (otherwise "Neplatný den")))
Zkrácené vyhodnocování. AND vrátí poslední pravdivý nebo NIL. OR vrátí první pravdivý nebo NIL.
; AND – vrátí NIL nebo poslední hodnotu (and 1 2 3) ; → 3 (and 1 nil 3) ; → NIL ; OR – vrátí první pravdivou hodnotu (or nil 42 100) ; → 42 (or nil nil) ; → NIL ; Praktické použití – defaultní hodnota (or vstup "výchozí")
Iterace a rekurze
LOOP, DOTIMES, DOLIST, DO, rekurze, tail call
; Jednoduchý nekonečný cyklus (loop (print "stále") (return)) ; RETURN ukončí smyčku ; FOR iterace přes čísla (loop for i from 1 to 5 do (format t "~a~%" i)) ; Sběr výsledků do seznamu (loop for i from 1 to 5 collect (* i i)) ; → (1 4 9 16 25) ; Filtrování a součet (loop for x in '(1 2 3 4 5 6 7 8) when (evenp x) sum x) ; → 20 (2+4+6+8) ; Více proměnných, podmínka ukončení (loop for i from 0 for j in '(a b c d) collect (list i j)) ; → ((0 A) (1 B) (2 C) (3 D)) ; Maximize, minimize, count (loop for x in '(3 1 4 1 5 9 2 6) maximize x) ; → 9
; DOTIMES – n opakování (0 to n-1) (dotimes (i 5) (format t "Řádek ~a~%" i)) ; 0 1 2 3 4 ; DOLIST – iterace přes seznam (dolist (barva '(červená zelená modrá)) (format t "Barva: ~a~%" barva))
; Faktoriál – rekurzivní (defun faktorial (n) (if (<= n 1) 1 (* n (faktorial (1- n))))) (faktorial 10) ; → 3628800 ; Fibonacci – tail recursive (defun fib (n &optional (a 0) (b 1)) (if (zerop n) a (fib (1- n) b (+ a b)))) (fib 10) ; → 55
Seznamy a operace s nimi
Cons buňky, CAR, CDR, LIST, APPEND, MAP, FILTER, REDUCE
(cons CAR CDR). CAR = první prvek, CDR = zbytek. Prázdný seznam NIL = konec řetězce.
; Vytvoření seznamu (list 1 2 3) ; → (1 2 3) '(a b c) ; → (A B C) – zkratka přes quote (cons 1 (cons 2 nil)) ; → (1 2) – ruční stavba ; Přístup k prvkům (car '(1 2 3)) ; → 1 (první prvek) (cdr '(1 2 3)) ; → (2 3) (zbytek) (cadr '(1 2 3)) ; → 2 (car z cdr = druhý) (caddr '(1 2 3)) ; → 3 (třetí) (first '(a b c)) ; → A (čitelnější alias) (second '(a b c)) ; → B (last '(1 2 3)) ; → (3) – seznam s posledním! (nth 2 '(a b c d)) ; → C (0-indexed) ; Délka a vlastnosti (length '(a b c)) ; → 3 (null nil) ; → T (prázdný?) (member 3 '(1 2 3 4)) ; → (3 4) (je prvek v seznamu?) ; Spojování a manipulace (append '(1 2) '(3 4) '(5)) ; → (1 2 3 4 5) (reverse '(1 2 3)) ; → (3 2 1) (sort '(3 1 4 1 5) #'<) ; → (1 1 3 4 5) (remove-duplicates '(1 2 2 3 3 3)) ; → (1 2 3) ; Funkcionální transformace (mapcar #'1+ '(1 2 3)) ; → (2 3 4) (remove-if #'evenp '(1 2 3 4 5)) ; → (1 3 5) (find-if #'evenp '(1 3 4 5)) ; → 4 (count-if #'oddp '(1 2 3 4 5)) ; → 3 (reduce #'+ '(1 2 3 4 5)) ; → 15 ; Asociativní seznam (alist) – klíč/hodnota (defvar *konfig* '((:host . "localhost") (:port . 5432) (:db . "mydb"))) (cdr (assoc :port *konfig*)) ; → 5432
Řetězce a formátování
STRING, FORMAT, CONCATENATE, SUBSEQ
(length "ahoj") ; → 4 (char "ahoj" 0) ; → #\a (znak na pozici) (subseq "ahoj svete" 5) ; → "svete" (subseq "ahoj svete" 0 4) ; → "ahoj" (concatenate 'string "Ahoj" " " "světe") ; → "Ahoj světe" (string-upcase "ahoj") ; → "AHOJ" (string-downcase "AHOJ") ; → "ahoj" (string-trim " " " hi ") ; → "hi" (search "světe" "ahoj světe") ; → 5 (string= "ahoj" "ahoj") ; → T (string-equal "AHOJ" "ahoj") ; → T (case-insensitive) ; Převody (write-to-string 42) ; → "42" (parse-integer "42") ; → 42 (read-from-string "(1 2 3)") ; → (1 2 3)
; FORMAT: (format cíl šablona &rest args) ; T = standardní výstup, NIL = vrátí řetězec (format t "Ahoj ~a!~%" "Karel") ; ~a = tiskni objekt (bez uvozovek) ; ~s = tiskni s uvozovkami/čitelně ; ~% = nový řádek ; ~& = nový řádek pokud není na začátku (format t "~a + ~a = ~a~%" 3 4 (+ 3 4)) ; 3 + 4 = 7 (format t "~d ~b ~o ~x~%" 42 42 42 42) ; 42 101010 52 2a ; ~d=decimal ~b=binárně ~o=oktalově ~x=hexa (format t "~{~a ~}~%" '(1 2 3)) ; 1 2 3 (~{...~} = iterace seznamu) (format nil "hodnota: ~a" 42) ; → "hodnota: 42" (vrátí jako řetězec)
Matematické funkce
Aritmetika, zlomky, bignums, vědecké funkce
| Funkce | Popis | Příklad → výsledek |
|---|---|---|
| + - * / | Základní aritmetika (n-ární!) | (+ 1 2 3 4) → 10 |
| 1+ 1- | Inkrementace / dekrementace | (1+ 5) → 6 |
| abs | Absolutní hodnota | (abs -7) → 7 |
| max min | Maximum / minimum (n-ární) | (max 3 1 4 1 5) → 5 |
| floor ceiling round truncate | Zaokrouhlování | (floor 3.7) → 3 |
| mod rem | Zbytek po dělení | (mod 10 3) → 1 |
| expt | Mocnění | (expt 2 10) → 1024 |
| sqrt | Druhá odmocnina | (sqrt 2) → 1.4142135 |
| log exp | Logaritmus / exponenciála | (log 100 10) → 2.0 |
| sin cos tan | Goniometrické funkce (radiány) | (sin pi) ≈ 0 |
| asin acos atan | Inverzní goniometrické | (atan 1) → pi/4 |
| gcd lcm | Největší společný dělitel / násobek | (gcd 12 8) → 4 |
| numerator denominator | Čitatel / jmenovatel zlomku | (numerator 3/4) → 3 |
| evenp oddp zerop plusp minusp | Predikáty | (evenp 4) → T |
| random | Náhodné číslo 0..n-1 | (random 6) → 0-5 |
; Přesné zlomky – žádná ztráta přesnosti! (+ 1/3 1/6) ; → 1/2 (ne 0.5000001) (* 3/7 7/3) ; → 1 (přesně) (/ 10 3) ; → 10/3 (ne 3.333...) ; Bignums – celá čísla libovolné velikosti (expt 2 100) ; → 1267650600228229401496703205376 (přesně!) (faktorial 50) ; → 30414093201713378043612608166979581188299763898377856000000000000 ; Komplexní čísla (sqrt -1) ; → #C(0.0 1.0) (= i) (+ #C(3 4) #C(1 -2)) ; → #C(4 2)
Struktury a hashtables
DEFSTRUCT, MAKE-HASH-TABLE, arrays
; Definice struktury (defstruct osoba jmeno (vek 0) ; výchozí hodnota email) ; Vytvoření instance (defvar *karel* (make-osoba :jmeno "Karel Novák" :vek 30 :email "karel@email.cz")) ; Přístup k polím (automaticky generované) (osoba-jmeno *karel*) ; → "Karel Novák" (osoba-vek *karel*) ; → 30 ; Modifikace (setf (osoba-vek *karel*) 31) ; Predikát (osoba-p *karel*) ; → T
; Hashovací tabulka (defvar *ht* (make-hash-table :test #'equal)) (setf (gethash "Karel" *ht*) 100) (setf (gethash "Jana" *ht*) 200) (gethash "Karel" *ht*) ; → 100 T (remhash "Karel" *ht*) ; smazat klíč (hash-table-count *ht*) ; → 1 ; Vektor (jednorozměrné pole) (defvar *v* (make-array 5 :initial-element 0)) (setf (aref *v* 2) 42) (aref *v* 2) ; → 42 ; Literál vektor (defvar *barvy* #("červená" "zelená" "modrá")) (aref *barvy* 1) ; → "zelená"
CLOS – Common Lisp Object System
DEFCLASS, DEFMETHOD, generické funkce, dědičnost, before/after
; Definice třídy (defclass zvire () ((jmeno :initarg :jmeno :accessor zvire-jmeno :initform "Neznámé") (hlas :initarg :hlas :accessor zvire-hlas :initform "...")) (:documentation "Základní třída pro všechna zvířata.")) ; Podtřída – dědí od zvire (defclass pes (zvire) ((plemeno :initarg :plemeno :accessor pes-plemeno))) (defclass kocka (zvire) ((indoor :initarg :indoor :accessor kocka-indoor :initform t))) ; Generická funkce + metody (defgeneric predstav-se (z) (:documentation "Zvíře se představí.")) (defmethod predstav-se ((z zvire)) (format t "Jsem ~a a říkám '~a'~%" (zvire-jmeno z) (zvire-hlas z))) (defmethod predstav-se ((p pes)) (call-next-method) ; zavolá metodu z předka (format t "Jsem ~a!~%" (pes-plemeno p))) ; Vytvoření instancí (defvar *rex* (make-instance 'pes :jmeno "Rex" :hlas "Haf!" :plemeno "Ovčák")) (defvar *micka* (make-instance 'kocka :jmeno "Míčka" :hlas "Mňau")) (predstav-se *rex*) ; Jsem Rex a říkám 'Haf!' + Jsem Ovčák! (predstav-se *micka*) ; Jsem Míčka a říkám 'Mňau' ; Before/after metody – AOP v Lispu (defmethod predstav-se :before ((z zvire)) (format t "--- Začátek představení ---~%")) (defmethod predstav-se :after ((z zvire)) (format t "--- Konec ---~%")) ; Kontrola instance (typep *rex* 'pes) ; → T (typep *rex* 'zvire) ; → T (dědičnost)
Makra – metaprogramování
DEFMACRO, backquote šablony, transformace kódu za kompilace
; Jednoduché makro – swap! (prohození dvou proměnných) (defmacro swap! (a b) (let ((tmp (gensym "TMP"))) ; gensym = unikátní symbol `(let ((,tmp ,a)) (setf ,a ,b) (setf ,b ,tmp)))) (defvar x 1) (defvar y 2) (swap! x y) ; x=2, y=1 ; Makro – "while" smyčka (CL ji nemá nativně) (defmacro while (podminka &body telo) `(loop while ,podminka do ,@telo)) (defvar n 0) (while (< n 5) (print n) (incf n)) ; Makro pro logování (defmacro with-logging (nazev &body telo) `(progn (format t ">>> Začátek: ~a~%" ,nazev) (let ((vysledek (progn ,@telo))) (format t "<<< Konec: ~a, výsledek: ~a~%" ,nazev vysledek) vysledek)))) (with-logging "výpočet" (+ 2 3)) ; >>> Začátek: výpočet ; <<< Konec: výpočet, výsledek: 5 ; MACROEXPAND – zobrazí expandovaný kód (pro ladění) (macroexpand-1 '(while (< n 5) (print n)))
Podmínky a výjimky (Condition System)
HANDLER-CASE, HANDLER-BIND, RESTART, IGNORE-ERRORS
; HANDLER-CASE – zachytí podmínku (jako try/catch) (handler-case (/ 10 0) ; chybný výraz (division-by-zero (e) (format t "Dělení nulou: ~a~%" e)) (error (e) (format t "Jiná chyba: ~a~%" e))) ; IGNORE-ERRORS – ignoruj chybu, vrať NIL (ignore-errors (/ 10 0)) ; → NIL (bez crash) ; Definice vlastní podmínky (define-condition moje-chyba (error) ((zprava :initarg :zprava :accessor chyba-zprava)) (:report (lambda (c stream) (format stream "Moje chyba: ~a" (chyba-zprava c))))) ; Vyvolání a zachycení vlastní podmínky (handler-case (error 'moje-chyba :zprava "Něco se pokazilo!") (moje-chyba (e) (format t "Zachyceno: ~a~%" (chyba-zprava e)))) ; UNWIND-PROTECT – garantuje provedení cleanup (jako finally) (unwind-protect (format t "Pracuji...~%") (format t "Cleanup – vždy se provede~%"))
Vstup a výstup
READ, PRINT, FORMAT, streamy
; PRINT – tiskne s newline, čitelně (s uvozovkami) (print "Ahoj") ; → "Ahoj" (s uvozovkami) ; PRINC – tiskne bez uvozovek (pro lidi) (princ "Ahoj") ; → Ahoj ; PRIN1 – jako PRINT ale bez newline (prin1 "Ahoj") ; → "Ahoj" ; TERPRI – odřádkování (terpri) ; FORMAT – nejflexibilnější (format t "~a ~s ~%" "ahoj" "ahoj") ; ahoj "ahoj" ; Výstup na chybový stream (format *error-output* "Varování!~%")
; READ – přečte a vrátí S-výraz (defvar vstup (read)) ; Uživatel zadá: (1 2 3) ; vstup = seznam (1 2 3) ; READ-LINE – přečte celý řádek jako řetězec (defvar radek (read-line)) ; Výzva uživateli (defun dotaz (text) (format t "~a: " text) (finish-output) ; vyprázdni buffer (read-line)) (defvar jmeno (dotaz "Tvoje jméno")) (format t "Ahoj, ~a!~%" jmeno) ; STRING stream – vstup/výstup do/z řetězce (defvar s (make-string-input-stream "42")) (read s) ; → 42
Práce se soubory
OPEN, WITH-OPEN-FILE, READ/WRITE
; WITH-OPEN-FILE – doporučený způsob (automaticky zavře soubor) ; ── ZÁPIS ───────────────────────────────────────── (with-open-file (stream "data.txt" :direction :output :if-exists :supersede) (format stream "Karel Novák~%") (format stream "30~%") (format stream "Praha~%")) ; ── ČTENÍ ŘÁDEK PO ŘÁDKU ────────────────────────── (with-open-file (stream "data.txt" :direction :input) (loop for radek = (read-line stream nil nil) while radek do (format t "~a~%" radek))) ; ── ČTENÍ S-VÝRAZŮ ──────────────────────────────── ; Uložíme Lispová data (with-open-file (s "config.lisp" :direction :output :if-exists :supersede) (print '(:verze "1.0" :debug t :port 8080) s)) ; Načteme zpět (with-open-file (s "config.lisp") (defvar *konfig* (read s))) ; Zjistit zda soubor existuje (probe-file "data.txt") ; → pathname nebo NIL ; Smazat soubor (delete-file "data.txt")
Celý program – Textová databáze kontaktů
Demonstruje CLOS, soubory, makra, podmínky a funkcionální přístup
;;;; ================================================ ;;;; DATABÁZE KONTAKTŮ – Common Lisp ;;;; Demonstruje: CLOS, soubory, funkcionální styl ;;;; ================================================ ;; ── Datový model ────────────────────────────────── (defclass kontakt () ((id :initarg :id :accessor kontakt-id) (jmeno :initarg :jmeno :accessor kontakt-jmeno) (telefon :initarg :telefon :accessor kontakt-telefon) (email :initarg :email :accessor kontakt-email)) (:documentation "Jeden záznam kontaktu.")) (defparameter *databaze* '() "Seznam všech kontaktů.") (defparameter *posledni-id* 0) ;; ── Pomocné funkce ──────────────────────────────── (defun nove-id () (incf *posledni-id*)) (defmethod print-object ((k kontakt) stream) (format stream "[~3d] ~20a ~15a ~a" (kontakt-id k) (kontakt-jmeno k) (kontakt-telefon k) (kontakt-email k))) ;; ── CRUD operace ────────────────────────────────── (defun pridat-kontakt (jmeno telefon email) "Přidá nový kontakt do databáze." (let ((k (make-instance 'kontakt :id (nove-id) :jmeno jmeno :telefon telefon :email email))) (push k *databaze*) k)) (defun najdi-kontakt (hledani) "Najde kontakty dle jména (case-insensitive)." (remove-if-not (lambda (k) (search (string-upcase hledani) (string-upcase (kontakt-jmeno k)))) *databaze*)) (defun smazat-kontakt (id) "Smaže kontakt dle ID." (setf *databaze* (remove-if (lambda (k) (= (kontakt-id k) id)) *databaze*))) (defun vypis-vsechny () (if (null *databaze*) (format t "Databáze je prázdná.~%") (dolist (k (reverse *databaze*)) (format t "~a~%" k)))) ;; ── Persistence ─────────────────────────────────── (defun uloz-databazi (soubor) (with-open-file (s soubor :direction :output :if-exists :supersede) (format s "~s~%" (mapcar (lambda (k) (list (kontakt-id k) (kontakt-jmeno k) (kontakt-telefon k) (kontakt-email k))) *databaze*)))) (format t "Uloženo do ~a~%" soubor)) ;; ── Spuštění – ukázková data ────────────────────── (pridat-kontakt "Karel Novák" "602 111 222" "karel@example.cz") (pridat-kontakt "Jana Nováková" "603 333 444" "jana@example.cz") (pridat-kontakt "Petr Kratochvíl" "604 555 666" "petr@work.cz") (format t "~%=== Všechny kontakty ===~%") (vypis-vsechny) (format t "~%=== Hledám 'novák' ===~%") (dolist (k (najdi-kontakt "novák")) (format t "~a~%" k)) (format t "~%Celkem: ~a kontaktů~%" (length *databaze*)) (uloz-databazi "kontakty.lisp")