(Common Lisp)

Referenční příručka s příklady a českými komentáři

ANSI CL Standard SBCL · CCL · CLISP SLIME · Quicklisp
λ

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

🔵 Homoikonicita: Program v Lispu je sám o sobě Lispovský seznam. To umožňuje makra, která transformují kód ještě před jeho spuštěním – mnohem výkonnější než textové preprocesory v jiných jazycích.
PojemVysvětlení
S-výrazZákladní syntaktická jednotka: atom nebo seznam
AtomČíslo, řetězec, symbol, znak
FormaS-výraz, který lze vyhodnotit
Speciální formaVestavěná syntaxe (IF, LET, DEFUN...)
MakroTransformace kódu za kompilace
REPLRead-Eval-Print Loop – interaktivní prostředí
PackageJmenný prostor pro symboly
GCAutomatická správa paměti
▶ Základní syntaxe – volání funkce = seznam
; 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álTypPříkladPoznámka
42INTEGER42, -7, 0Celé číslo, libovolná velikost (bignum)
3.14FLOAT3.14, 1.0e10Desetinné číslo
1/3RATIO1/3, 22/7Př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
#\ACHARACTER#\A #\Space #\NewlineJednotlivý znak
symbolSYMBOLx, moje-funkce, +Identifikátor (case-insensitive!)
:klíčovéKEYWORD:name :color :xSymbol začínající dvojtečkou
TBOOLEANTPravda (true)
NILBOOLEAN / NULLNIL, '()Nepravda i prázdný seznam
▶ Kvotování a evaluace
; 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átTestujePříklad → výsledek
numberp xJe číslo?(numberp 42) → T
integerp xJe celé číslo?(integerp 3.0) → NIL
floatp xJe float?(floatp 3.14) → T
rationalp xJe racionální?(rationalp 1/3) → T
stringp xJe řetězec?(stringp "hi") → T
symbolp xJe symbol?(symbolp 'x) → T
listp xJe seznam?(listp '(1 2)) → T
null xJe NIL?(null nil) → T
consp xJe neprázdný pár?(consp '(1)) → T
functionp xJe funkce?(functionp #'+) → T
characterp xJe znak?(characterp #\A) → T
arrayp xJe pole?(arrayp #(1 2 3)) → T
keywordp xJe klíčové slovo?(keywordp :name) → T
typep x typJe x daného typu?(typep 3 'integer) → T
🔤

Proměnné a vazby

DEFVAR, DEFPARAMETER, DEFCONSTANT, LET, LET*, SETF

defvar makro

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!
defparameter makro

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í.")
defconstant makro

Definuje konstantu – hodnotu nelze měnit za běhu.

(defconstant +pi+ 3.14159265358979)
(defconstant +max-velikost+ 1024)

; Konvence: konstanty v +plusech+
let / let* speciální forma

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
setf / setq makro / speciální forma

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

▶ DEFUN – definice pojmenované funkce
; 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, uzávěry (closures) a funkce vyššího řádu
; 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

ifspeciální forma

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 / unlessmakro

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"))
condspeciální forma

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ě"
casemakro

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")))
and / ormakro

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

▶ LOOP – mocný makrojazyk pro iteraci
; 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 a DOLIST
; 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))
▶ Rekurze s tail call
; 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 buňka: Základní stavební kámen Lispu. Každý seznam je řetězec cons buněk: (cons CAR CDR). CAR = první prvek, CDR = zbytek. Prázdný seznam NIL = konec řetězce.
▶ Základní operace se seznamy
; 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

▶ Základní operace s řetězci
(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 – výstupní formátování
; 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

FunkcePopisPříklad → výsledek
+ - * /Základní aritmetika (n-ární!)(+ 1 2 3 4) → 10
1+ 1-Inkrementace / dekrementace(1+ 5) → 6
absAbsolutní hodnota(abs -7) → 7
max minMaximum / minimum (n-ární)(max 3 1 4 1 5) → 5
floor ceiling round truncateZaokrouhlování(floor 3.7) → 3
mod remZbytek po dělení(mod 10 3) → 1
exptMocnění(expt 2 10) → 1024
sqrtDruhá odmocnina(sqrt 2) → 1.4142135
log expLogaritmus / exponenciála(log 100 10) → 2.0
sin cos tanGoniometrické funkce (radiány)(sin pi) ≈ 0
asin acos atanInverzní goniometrické(atan 1) → pi/4
gcd lcmNejvětší společný dělitel / násobek(gcd 12 8) → 4
numerator denominatorČitatel / jmenovatel zlomku(numerator 3/4) → 3
evenp oddp zerop plusp minuspPredikáty(evenp 4) → T
randomNáhodné číslo 0..n-1(random 6) → 0-5
▶ Unikátní vlastnosti – přesná aritmetika a bignums
; 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

▶ DEFSTRUCT – struktury (záznamy)
; 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
▶ Hash table a vektory
; 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

🔵 CLOS je jedním z nejpokročilejších objektových systémů vůbec. Metody patří generickým funkcím, ne třídám. Dispatch (výběr metody) může probíhat podle typů více argumentů (multiple dispatch). Metody mohou být složeny pomocí :before, :after a :around.
▶ Třídy, metody a dědičnost
; 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

⚠️ Makra vs. funkce: Makra dostávají argumenty jako nevyhodnocené S-výrazy a transformují je na jiný kód. Výsledný kód se pak vyhodnotí. Makra jsou mocnější, ale je třeba dávat pozor na hygienická pravidla (náhodné zachycení proměnných → GENSYM).
▶ Definice a použití maker
; 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

🔵 Condition System je nejpokročilejší systém pro ošetřování chyb v jakémkoliv jazyce. Na rozdíl od Javy nebo Pythonu umožňuje restarty – kód pro ošetření chyby může pokračovat tam, kde nastala, bez rozvinutí zásobníku.
▶ Ošetřování chyb
; 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

▶ Výstup
; 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í!~%")
▶ Vstup
; 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

▶ Zápis, čtení a manipulace souborů
; 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

▶ kontakty.lisp – CRUD aplikace v Common Lispu
;;;; ================================================
;;;; 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")
💡 Kde spustit? Nainstaluj SBCL nebo CLISP. Pro komfortní vývoj doporučujeme SLIME (Emacs) nebo Portacle (all-in-one). Správce balíčků: Quicklisp.