Úpravy Emacsu s Emacs Lisp: dokončení popisu Emacs Lispu

20. 9. 2018
Doba čtení: 26 minut

Sdílet

V posledním článku, který se bude zabývat výhradně Emacs Lispem a nikoli samotným Emacsem, si popíšeme ostatní datové typy, s nimiž se v Emacs Lispu setkáme. Jedná se o vektory, pole, asociativní pole, řetězce a další.

Obsah

1. Úpravy Emacsu s Emacs Lisp: dokončení popisu Emacs Lispu

2. Sekvence – seznamy a pole

3. Operace nad sekvencemi: predikát, kopie, získání prvku a délky sekvence

4. Konstruktory vektorů

5. Další funkce určené pro práci s vektory

6. Základní funkce pro práci s poli

7. Čtení a zápis prvků polí

8. Kopie seznamů a polí

9. Základní vlastnosti řetězců

10. Kódy znaků v Emacsu

11. Regulární výrazy

12. Nahrazení části řetězce jiným obsahem

13. Hešovací tabulky

14. Konstruktor hešovacích tabulek

15. Funkce pro práci s hešovacími tabulkami, řešení kolize klíčů

16. Funkce maphash

17. Asociativní seznamy a seznamy vlastností

18. Repositář s demonstračními příklady

19. Literatura

20. Odkazy na Internetu

1. Úpravy Emacsu s Emacs Lisp: dokončení popisu Emacs Lispu

Na předchozí čtyři články, v nichž jsme se seznámili jak se základy programovacího jazyka Emacs Lisp [1] [2], tak i s užitečným makrem cl-loop a s knihovnou Dash, dnes navážeme. Popíšeme si totiž všechny další strukturované datové typy, s nimiž se v tomto programovacím jazyce můžeme setkat. Jedná se zejména o vektory a taktéž o řetězce, které mají hodně společného. S řetězci samozřejmě souvisí funkce pro vyhledání podle regulárního výrazu či náhrada na základě regulárního výrazu. Nesmíme ovšem zapomenout ani na velmi užitečné hešovací tabulky, asociativní seznamy a seznamy vlastností.

2. Sekvence – seznamy a pole

V Emacs Lispu se setkáme s pojmem „sekvence“. Jedná se o obecnější označení seznamů (list) a polí (array), přičemž seznamy již známe a víme, že jsou plnohodnotným LISPovským datovým typem (navíc díky homoikonicitě je samotný program reprezentován seznamem). Pole ovšem není konkrétním datovým typem, ale označením pro skupinu dalších typů, konkrétně vektorů (vector), řetězců (string) a speciálních typů, mezi něž patří především bool-vector a char-table. Vztahy mezi obecnými typy a typy konkrétními je naznačen na následujícím obrázku:

Obrázek 1: Hierarchie sekvenčních datových typů v Emacs Lispu. Ve skutečnosti existují čtyři konkrétní implementace polí, protože k vektorům a řetězcům je možné přidat i poněkud speciální typy bool-vector a char-table zmíněné v předchozím textu.

V následující tabulce jsou vypsány některé společné vlastnosti ale i obecně platné rozdíly mezi seznamy a poli (vektory, řetězci):

Vlastnost Seznamy Vektory Řetězce
typ struktury heterogenní heterogenní je homogenní
přístup k prvku přes index O(n), lineární O(1), konstantní O(1), konstantní
index prvního prvku 0 0 0
přidání prvku na začátek struktury lze nelze nelze
odebrání prvního prvku ze struktury lze nelze nelze
konstruktor '(prvek prvek prvek} [prvek prvek prvek] „řetězec“
velikost při konstrukci nemusí být známá musí být známá musí být známá
Poznámka: zde je nutno poznamenat, že například v programovacím jazyku Clojure jsou vektory implementovány odlišně a při přidávání a ubírání prvků je možné sdílet strukturu původního vektoru (structural sharing). Přístup k prvkům však již z tohoto důvodu není striktně konstantní, i když složitost je velmi nízká a prakticky se blíží ke složitosti konstantní: O(log32N).

3. Operace nad sekvencemi: predikát, kopie, získání prvku a délky sekvence

Pro práci se všemi typy sekvencí (seznam, vektor, řetězec, …) existuje čtveřice obecných funkcí. Především se jedná o test, zda je nějaká hodnota libovolnou sekvencí, dále o funkci pro zjištění délky sekvence, přístup k n-tému prvku přes jeho index a konečně o funkci vytvářející kopii sekvence stejného typu:

Funkce Stručný popis
sequencep predikát vracející t pouze tehdy, pokud je mu předána libovolná sekvence
length výpočet délky sekvence (funkce může být relativně pomalá pro seznamy)
elt vrátí n-tý prvek v sekvenci, pokud má samozřejmě sekvence alespoň n prvků (indexuje se od nuly)
copy-sequence kopie sekvence, ovšem nikoli hluboká kopie (ozřejmíme si později)

Nejprve si vytvořme tři sekvence, každou jiného typu – seznam, vektor, řetězec:

(setq l1 '(1 2 3 4 5))
(setq v1 [1 2 3 4 5])
(setq s1 "Hello")

Dále si vytvoříme tři prázdné sekvence:

(setq l2 '())
(setq v2 [])
(setq s2 "")

Nyní můžeme predikátem sequencep snadno zjistit, že všech šest proměnných referencuje nějakou sekvenci:

(print (sequencep l1))
(print (sequencep v1))
(print (sequencep s1))
(print (sequencep l2))
(print (sequencep v2))
(print (sequencep s2))

Výsledek:

t
t
t
t
t
t

Zjištění délky sekvencí funkcí length:

(print (length l1))
(print (length v1))
(print (length s1))
(print (length l2))
(print (length v2))
(print (length s2))

Výsledek:

5
5
5
0
0
0

Přístup ke třetímu, popř. ke 101 prvku (ten neexistuje):

(print (elt l1 2))
(print (elt v1 2))
(print (elt s1 2))
 
(print (elt l2 100))
(print (elt v2 100))
(print (elt s2 100))

S výsledky (povšimněte si, že prázdný seznam je zpracován speciálním způsobem, protože je roven nil):

3
3
108
 
nil
Args out of range: [], 100

A konečně můžeme získat kopie sekvencí s využitím funkce copy-sequence:

(setq l1 '(1 2 3 4 5))
(setq v1 [1 2 3 4 5])
(setq s1 "Hello")
 
(setq l2 (copy-sequence l1))
(setq v2 (copy-sequence v1))
(setq s2 (copy-sequence s1))
 
(print (sequencep l1))
(print (sequencep v1))
(print (sequencep s1))
(print (sequencep l2))
(print (sequencep v2))
(print (sequencep s2))
 
(print "-----------------------------------------")
 
(print l1)
(print v1)
(print s1)
(print l2)
(print v2)
(print s2)

Výsledky:

t
t
t
t
t
t
"-----------------------------------------"
(1 2 3 4 5)
[1 2 3 4 5]
"Hello"
(1 2 3 4 5)
[1 2 3 4 5]
"Hello"

4. Konstruktory vektorů

V této kapitole se nejprve zmíníme o základních vlastnostech takzvaných vektorů:

  1. Vektory obsahují uspořádanou sekvenci hodnot. Tato sekvence zachovává pořadí prvků určené uživatelem (na rozdíl od množin atd.)
  2. Prvky vektorů mohou být libovolného a od sebe odlišného typu, tj. jedná se o heterogenní datovou strukturu (na rozdíl od polí v některých jiných programovacích jazycích).
  3. Hodnotu prvku lze za určitých okolností měnit, vektor tedy není striktně neměnitelný (immutable).
  4. Prvky však nelze ani přidávat ani ubírat, takže tvar vektoru (shape) je zachován. Touto vlastností se vektory přibližují klasickým polím z jiných programovacích jazyků.

Konstrukci vektoru lze provést buď zápisem [prvek prvek prvek], nebo je možné použít jednu z funkcí pojmenovaných vector a make-vector:

Funkce Stručný popis funkce
vector vytvoří vektor z předaných prvků
make-vector vytvoří vektor opakováním zadaného prvku

Podívejme se nyní na příklad, v němž je vytvořeno sedm vektorů, včetně vektoru prázdného a vektoru nehomogenního (s různými typy prvků):

(setq v1 [1 2 3 4 5])
(setq v2 [])
(setq v3 (vector))
(setq v4 (vector 1 2 3 4 5))
(setq v5 (vector '1 :2 "3" '(4 5) [6 7]))
(setq v6 (make-vector 10 "foo"))
(setq v7 (make-vector 0 "foo"))
Poznámka: povšimněte si, že funkci make-vector musíte předat počet prvků, který ovšem může být i nulový (nikoli však záporný).

Výpis hodnot všech vektorů zajistí funkce print:

(print v1)
(print v2)
(print v3)
(print v4)
(print v5)
(print v6)
(print v7)

Výsledky:

[1 2 3 4 5]
[]
[]
[1 2 3 4 5]
[1 :2 "3" (4 5) [6 7]]
["foo" "foo" "foo" "foo" "foo" "foo" "foo" "foo" "foo" "foo"]
[]

5. Další funkce určené pro práci s vektory

Mezi další funkce určené pro zpracování vektorů patří především tato čtveřice:

Funkce Stručný popis funkce
vectorp test (predikát), zda je hodnota vektorem
vconcat všechny prvky předaných sekvencí se transformují do vektoru
length výpočet délky vektoru (již vlastně známe)
append konverze vektoru na seznam s případným připojením prvku

Všechny tyto funkce budou ukázány v následujících příkladech.

Vytvoříme tři sekvence, ovšem jen jedna sekvence bude vektorem:

(setq s1 '(1 2 3 4))
(setq v1 [1 2 3 4])
(setq n1 42)
(setq str1 "Hello")

Použití predikátu vectorp:

(print (vectorp s1))
(print (vectorp v1))
(print (vectorp n1))
(print (vectorp str1))

Výsledky:

nil
t
nil
nil

Vytvoření různých vektorů konstruktorem vector.

(setq v3 (vector))
(setq v4 (vector 1 2 3 4))
(setq v5 (vector '1 :2 "3" '(4 5) [6 7]))

Opětovné použití predikátu vectorp:

(print (vectorp v3))
(print (vectorp v4))
(print (vectorp v5))

Výsledky:

t
t
t

Použití funkce vconcat (povšimněte si různé struktury prvků):

(setq v1 (vconcat '(A B C) '(D E F)))
(setq v2 (vconcat [A B C] '(D E F)))
(setq v3 (vconcat '() []))
(setq v4 (vconcat [[1 2] [3 4]]))
(setq v5 (vconcat '() [[1 2] [3 4]]))
 
(print v1)
(print v2)
(print v3)
(print v4)
(print v5)

Výsledky (na nejvyšší úrovni byly původní struktury „zploštěny“):

[A B C D E F]
[A B C D E F]
[]
[[1 2] [3 4]]
[[1 2] [3 4]]

Převod různých vektorů na seznamy pomocí funkce append:

(setq v1 [1 2 3 4 5])
(setq v2 [])
(setq v3 (vector))
(setq v4 (vector 1 2 3 4 5))
(setq v5 (vector '1 :2 "3" '(4 5) [6 7]))
(setq v6 (make-vector 10 "foo"))
(setq v7 (make-vector 0 "foo"))
 
(print (append v1 nil))
(print (append v2 nil))
(print (append v3 nil))
(print (append v4 nil))
(print (append v5 nil))
(print (append v6 nil))
(print (append v7 nil))

Výsledky jsou nyní předvídatelné:

(1 2 3 4 5)
nil
nil
(1 2 3 4 5)
(1 :2 "3" (4 5) [6 7])
("foo" "foo" "foo" "foo" "foo" "foo" "foo" "foo" "foo" "foo")
nil
Poznámka: opět můžeme vidět, že prázdný seznam je ekvivalentní hodnotě nil.

6. Základní funkce pro práci s poli

Dalšími funkcemi, s nimiž se seznámíme, jsou funkce použitelné pro práci s poli, tj. především s vektory a s řetězci. Nejedná se tedy o funkce tak specializované, jako funkce popsané v předchozí kapitole, na druhou stranu ovšem nejsou tak obecné, jako funkce pro práci s libovolnou sekvencí:

Funkce Stručný popis funkce
arrayp test (predikát), zda je hodnot nějakým polem
aref získání reference na i-tý prvek v poli (indexace)
aset zápis do i-tého prvku v poli
fillarray vyplnění celého pole prvky se shodnou hodnotou
Poznámka: opět pro porovnání – v jazyku Clojure existují funkce pojmenované aset a aget, které slouží pro zápis popř. pro čtení prvků z javovských polí.

Všechny čtyři výše popsané funkce si samozřejmě opět otestujeme:

(setq s1 '(1 2 3 4))
(setq v1 [1 2 3 4])
(setq n1 42)
(setq str1 "Hello")
 
; otestování predikátu arrayp
(print (arrayp s1))
(print (arrayp v1))
(print (arrayp n1))
(print (arrayp str1))
 
(print "-----------------------------------------")
 
; predikát pro vektory
(setq v3 (vector))
(setq v4 (vector 1 2 3 4))
(setq v5 (vector '1 :2 "3" '(4 5) [6 7]))
 
(print (arrayp v3))
(print (arrayp v4))
(print (arrayp v5))

Výsledky jsou předvídatelné:

nil
t
nil
t
"-----------------------------------------"
t
t
t

Otestování funkce fillarray aplikované na vektor čísel a řetězec:

(setq v1 [1 2 3 4])
(setq str1 "Hello")
 
(print v1)
(print str1)
 
(print "-----------------------------------------")
 
(fillarray v1 0)
(fillarray str1 ?*)
 
(print v1)
(print str1)

Výsledky:

[1 2 3 4]
"Hello"
"-----------------------------------------"
[0 0 0 0]
"*****"
Poznámka: zde můžeme vidět, že se řetězce skutečně chovají stejně jako vektory. Ve skutečnosti se totiž jedná o vektor celých čísel.

7. Čtení a zápis prvků polí

Pro čtení prvků z pole se používá funkce aref, pro zápis (modifikaci) pole pak funkce aset. Těmto funkcím se nejdříve předá pole a posléze index prvku. Funkci aset samozřejmě ještě modifikovaná hodnota:

(setq v1 [1 2 3 4])
(setq str1 "Hello")
 
(print v1)
(print str1)
 
(print "-----------------------------------------")
 
(aset v1 1 99)
(aset str1 0 ?*)
(aset str1 4 ?!)
 
(print v1)
(print str1)
 
(print "-----------------------------------------")
 
(print(aref v1 0))
 
(print(aref str1 0))
(print(aref str1 1))

Výsledky:

[1 2 3 4]
"Hello"
"-----------------------------------------"
[1 99 3 4]
"*ell!"
"-----------------------------------------"
1
42
101
Poznámka: povšimněte si, že se jednotlivé znaky zapisují s otazníkem na začátku. Dále pak je zajímavé, že aref nad řetězcem vrátí číslo znaku (což je sice jedno a totéž, ovšem funkce print vypíše číslo).

8. Kopie seznamů a polí

S kopií seznamů a polí jsme se ve skutečnosti už seznámili, protože pro tento účel je možné použít univerzální funkci nazvanou copy-sequence. Ve chvíli, kdy jsou kopírované sekvence jednoúrovňové, nebývá s použitím této funkce spojen prakticky žádný problém:

(setq v1 [1 2 3 4 5])
(setq s1 "Hello")
 
(setq v2 (copy-sequence v1))
(setq s2 (copy-sequence s1))
 
(print v1)
(print s1)
(print v2)
(print s2)

Výsledky:

[1 2 3 4 5]
"Hello"
[1 2 3 4 5]
"Hello"

V případě, že změníme prvek v původní sekvenci, nebude tato změna v kopiích nijak reflektována:

(aset v1 0 99)
(aset s1 0 ??)
 
(print v1)
(print s1)
(print v2)
(print s2)

Výsledky:

[99 2 3 4 5]
"?ello"
[1 2 3 4 5]
"Hello"

Zajímavější situace ovšem nastane, pokud je vstupní sekvence složitější o obsahuje například další vektory nebo řetězce:

(setq x [1 2 [3 4] [5 6]])
(setq y (copy-sequence x))
 
(print x)
(print y)

Výsledky prozatím odpovídají předpokladům:

[1 2 [3 4] [5 6]]
[1 2 [3 4] [5 6]]

Ovšem ve chvíli, kdy přes aref získáme referenci na vnořený seznam/řetězec a změníme ho pomocí aset, ukáže se, že původní i zkopírovaná sekvence obsahuje reference na shodné hodnoty:

(aset (aref x 2) 0 -1)
(aset (aref x 2) 1 -1)
 
(print x)
(print y)

Z výsledků je patrné, že se „nenápadně“ změnila o hodnota sekvence y:

[1 2 [-1 -1] [5 6]]
[1 2 [-1 -1] [5 6]]

9. Základní vlastnosti řetězců

Dalším typem pole jsou klasické řetězce. Interně se řetězce chovají prakticky stejně jako vektory celočíselných hodnot, což znamená, že vektory a řetězce mají velmi mnoho společných vlastností zmíněných ve čtvrté kapitole. Řetězce lze samozřejmě zkonstruovat s využitím řetězcového literálu popř. pomocí funkce string, které se předají buď jednotlivé znaky (zapisuje se před nimi prefix „?“) nebo celočíselné kódy jednotlivých znaků:

(setq s1 "Hello")
(setq s2 (make-string 10 ?*))
(setq s3 (string ?a ?b ?c))
(setq s4 (string 64 65 32 95 96 32))
 
(print s1)
(print s2)
(print s3)
(print s4)

Výsledky:

"Hello"
"**********"
"abc"
"@A _` "

Užitečná je funkce concat vracející nový řetězec vytvořený spojením libovolného množství vstupních řetězců:

(print (concat s1 s2 s3 s4))
 
"Hello**********abc@A _` "

Nesmíme zapomenout ani na další velmi užitečnou funkci pojmenovanou substring. Jak již název této funkce napovídá, lze s ní vytvořit podřetězec zadáním indexu prvního a posledního znaku v řetězci zdrojovém. První znak v řetězci má index roven 0, podobně jako první prvek v seznamu nebo ve vektoru. Index může být kladný (počítá se od začátku řetězce) nebo záporný (počítá se od konce řetězce). Druhý index může mít hodnotu nil znamenající konec řetězce, popř. nemusí být uveden vůbec:

(setq s1 "Hello world")
 
(print s1)
 
(print (substring s1 6 11))
(print (substring s1 0 -6))
 
(print (substring s1 6 nil))
(print (substring s1 -5 nil))
(print (substring s1 -5))
 
; kopie retezce, podobne copy-sequence
(print (substring s1 0))

S výsledky:

"Hello world"
"world"
"Hello"
"world"
"world"
"world"
"Hello world"

10. Kódy znaků v Emacsu

V Emacsu je možné znaky reprezentovat buď hodnotami od 0 do 255 (unibyte) nebo hodnotami od 0 do 4194303 (0×3FFFFF), které dokážou reprezentovat libovolný ASCII znak či Unicode znak (multibyte). Posledních 128 hodnot má pak speciální význam, protože reprezentují osmibitové hodnoty s horním bitem nastaveným na jedničku (to je ovšem skutečně specialita, kterou se nyní nemusíme zabývat). Pokud se řetězce konstruují pomocí string, můžeme této funkci předat kódy znaků:

(setq s1 (string 64 65 32 95 96 32))
(print s1)
 
"@A _` "

Popř. lze použít hexadecimální čísla (s poněkud neobvyklým zápisem):

(setq s2 (string #x40 #x41 #x20 #x60 #x20))
(print s2)
 
"@A ` "

Vzhledem k dobré podpoře Unicode si samozřejmě můžeme vytvořit řetězec se znaky alfabety nebo použít kódy dalších znaků a paznaků:

(setq s2 (string #x03b1 #x03b2 #x03c9))
(print s2)
 
"αβω"

11. Regulární výrazy

Emacs Lisp jakožto skriptovací jazyk, na němž je postaven plnohodnotný textový editor, samozřejmě podporuje i práci s regulárními výrazy. Základem je funkce nazvaná string-match, která se snaží nalézt ve vstupním řetězci první výskyt sekvence znaků, které odpovídají regulárnímu výrazu. Podporovány jsou jak základní operátory pro opakování tzv. atomů (?, +, *), tak i například kolekce znaků ([0–9]) a třídy znaků ([[:digit:]]). Podívejme se na jednoduchý příklad:

(setq s1 "Hello world 123456")
 
(print s1)
 
(print (string-match ".+" s1))
(print (string-match "xyz" s1))
 
(print (string-match "[0-9]+" s1))
(print (string-match "[[:digit:]]+" s1))
(print (string-match "[[:blank:]]" s1))
(print (string-match "[^A-Za-z]+" s1))
(print (string-match "[^A-Za-z ]+" s1))

Z výsledků je patrné, že tato funkce vrací index prvního výskytu popř. hodnotu nil v případě, že regulárnímu výrazu neodpovídá žádná část zdrojového řetězce:

"Hello world 123456"
0
nil
12
12
5
5
12
Poznámka: s regulárními výrazy se ještě několikrát setkáme při popisu funkcí určených pro zpracování textů v bufferech editoru Emacs.

12. Nahrazení části řetězce jiným obsahem, rozdělení řetězce

Další užitečnou funkcí, která se při zpracování řetězců poměrně často používá, je funkce nazvaná replace-regexp-in-string. Tato funkce umožňuje ve vstupním řetězci nalézt sekvenci znaků, samozřejmě opět na základě nějakého regulárního výrazu, a následně tuto sekvenci nahradit předaným řetězcem. Vzhledem k tomu, že se obecně změní délka řetězce (což u polí není možné), je ve skutečnosti touto funkcí vrácen řetězec nový.

opět si ukažme jednoduchý příklad:

(setq s1 "Hello world 123456")
 
(print s1)
 
(print (replace-regexp-in-string "[0-9]+" "*" s1))
(print (replace-regexp-in-string "world" "Emacs" s1))

Výsledky:

"Hello world 123456"
 
"Hello world *"
 
"Hello Emacs 123456"

Poslední „řetězcovou“ funkcí, s níž se dnes seznámíme, je funkce split-string určená pro rozdělení řetězce na (několik) částí, a to v místě specifikovaného znaku. Výsledkem je seznam kratších řetězců:

(setq s1 "Hello world 123456")
 
(print (split-string s1))

Výsledek:

("Hello" "world" "123456")

Použít lze i řídicí znaky, například znak pro konec řádku atd.:

(setq s2 "This\nis\nmultiline\nstring")
(print s2)
(print (split-string s2 "\n"))

Výsledek:

"This
is
multiline
string"
 
("This" "is" "multiline" "string")

13. Hešovací tabulky

Všechny prozatím popsané datové struktury reprezentovaly buď atomy nebo sekvenční (heterogenní) datové typy, v nichž byly všechny prvky adresovány celočíselným indexem (začínajícím od nuly). V praxi, tj. při psaní modulů pro Emacs, se ovšem velmi často setkáme i s hešovacími tabulkami, v nichž jsou uloženy dvojice klíč-hodnota, přičemž klíč nemusí být celočíselný (může být prakticky jakéhokoli typu podporovaného Emacs Lispem). Oproti ostatním heterogenním strukturovaným datovým typům se hešovací tabulky odlišují taktéž v tom ohledu, že se v nich nezachovává pořadí vložených prvků (to nám však u mnoha operací nemusí vadit a pokud ano, lze někdy použít asociativní seznamy nebo seznamy vlastností). Základní způsoby použití hešovacích tabulek je zmíněno v navazujících kapitolách.

14. Konstruktor hešovacích tabulek

Prázdná hešovací tabulka se vytvoří pomocí funkce pojmenované make-hash-table, které je možné v případě potřeby předat i další parametry popisující chování tzv. hešovací funkce, plánovanou kapacitu (počet prvků) a popř. i pravidla určující, kdy a do jaké míry se má hešovací tabulka zvětšit v případě, že již nestačí její kapacita. Ovšem při základním použití funkce pro vytvoření hešovací tabulky tyto údaje nepotřebujeme:

(setq hash1 (make-hash-table))

Mezi další nepovinné parametry patří například:

Jméno Význam
:test funkce pro zjištění ekvivalence klíčů při zápisu/vyhledávání
:size přibližná velikost hešovací tabulky (implicitně 65)
:rehash-size míra zvětšení tabulky při jejím zaplnění
:rehash-threshold desetinné číslo udávající práh, kdy je již tabulka považována za zaplněnou
Poznámka: pro tabulky s počtem prvků dosahujících řádově několik set pravděpodobně nemá smysl s těmito hodnotami laborovat.

Alternativně je možné použít i vytvoření hešovací tabulky zápisem její „textové podoby“, tj. takovým způsobem, jakým se obsah hešovací tabulky vypisuje na standardní výstup funkcí print:

(setq hash2 #s(hash-table size 30 data (key1 val1 key2 val2 key3 val3)))

Samozřejmě si můžete pro větší přehlednost tento zápis rozepsat na více řádků:

(setq hash2
      #s(hash-table
         size 30
         data (
               key1 val1
               key2 val2
               key3 val3)))

15. Funkce pro práci s hešovacími tabulkami, řešení kolize klíčů

Pro práci s hešovacími tabulkami je v Emacs Lispu určeno několik funkcí, které jsou vypsány níže společně s jejich stručným popisem:

Funkce Stručný popis funkce
make-hash-table vytvoření hešovací tabulky, již známe z předchozí kapitoly
gethash vyhledání prvku v hešovací tabulce
puthash vložení další dvojice klíč-hodnota do hešovací tabulky
remhash odstranění dvojice klíče-hodnota z hešovací tabulky
clrhash vymazání celého obsahu hešovací tabulky
maphash bude popsána v navazující kapitole

Při použití klíčů si musíme dát pozor na to, jaká funkce se volá pro zjištění, zda jsou klíče ekvivalentní. V níže uvedeném příkladu se nám stane, že řetězec „klic“ je použit dvakrát, protože výchozí porovnávací funkce nepovažuje dva řetězce se stejným obsahem ale jinou adresou za ekvivalentní:

(setq hash1 (make-hash-table))
(setq hash2 #s(hash-table size 30 data (key1 val1 key2 val2)))
 
(print hash1)
(print hash2)
 
(puthash 'klic 'hodnota hash1)
(puthash "klic" "hodnota" hash1)
(print hash1)
 
(puthash 'klic "jina hodnota" hash1)
(print hash1)
 
(puthash "klic" "uplne jina hodnota?" hash1)
(print hash1)

Výsledky (shodné klíče jsou zvýrazněny):

#s(hash-table size 65 test eql rehash-size 1.5 rehash-threshold 0.8 data ())
 
#s(hash-table size 30 test eql rehash-size 1.5 rehash-threshold 0.8 data (key1 val1 key2 300))
 
#s(hash-table size 65 test eql rehash-size 1.5 rehash-threshold 0.8 data (klic hodnota "klic" "hodnota"))
 
#s(hash-table size 65 test eql rehash-size 1.5 rehash-threshold 0.8 data (klic "jina hodnota" "klic" "hodnota"))
 
#s(hash-table size 65 test eql rehash-size 1.5 rehash-threshold 0.8 data (klic "jina hodnota" "klic" "hodnota" "klic" "uplne jina hodnota?"))

Náprava je snadná – explicitní určení funkce použité pro porovnávání klíčů:

(setq hash3 (make-hash-table :test 'equal))
 
(puthash 'klic 'hodnota hash3)
(puthash "klic" "hodnota" hash3)
(print hash3)
 
(puthash 'klic "jina hodnota" hash3)
(print hash3)
 
(puthash "klic" "uplne jina hodnota?" hash3)

Nyní je již vše v pořádku a prvek byl přepsán novou hodnotou:

#s(hash-table size 65 test equal rehash-size 1.5 rehash-threshold 0.8 data (klic hodnota "klic" "hodnota"))
 
#s(hash-table size 65 test equal rehash-size 1.5 rehash-threshold 0.8 data (klic "jina hodnota" "klic" "hodnota"))
 
#s(hash-table size 65 test equal rehash-size 1.5 rehash-threshold 0.8 data (klic "jina hodnota" "klic" "uplne jina hodnota?"))

16. Funkce maphash

Další funkcí použitou pro práci s hešovacími tabulkami je funkce nazvaná maphash. Jedná se o funkci vyššího řádu, která mapuje jinou uživatelem definovanou funkci na každou nalezenou dvojici klíč-hodnota. Funkce maphash kupodivu nevrací žádný výsledek, tj. ani výsledek mapování uživatelské funkce. Celé chování tedy závisí na vedlejších efektech:

(defun print-key-value (key value)
  (print (format "%s %d" (reverse key) (* 2 value))))
 
(setq hash1 (make-hash-table))
 
(print hash1)
 
(puthash "one" 1 hash1)
(puthash "two" 2 hash1)
(puthash "three" 3 hash1)
(puthash "four" 4 hash1)
(print hash1)
 
(maphash 'print-key-value hash1)
(print hash1)

Podívejme se na výsledky předchozího skriptu.

Původní prázdná tabulka a naplněná tabulka:

#s(hash-table size 65 test eql rehash-size 1.5 rehash-threshold 0.8 data ())
 
#s(hash-table size 65 test eql rehash-size 1.5 rehash-threshold 0.8 data ("one" 1 "two" 2 "three" 3 "four" 4))

Výsledek aplikace funkce print-key-value:

"eno 2"
 
"owt 4"
 
"eerht 6"
 
"ruof 8"

Původní tabulka nebyla funkcí maphash nijak dotčena:

#s(hash-table size 65 test eql rehash-size 1.5 rehash-threshold 0.8 data ("one" 1 "two" 2 "three" 3 "four" 4))

17. Asociativní seznamy a seznamy vlastností

Poslední dva strukturované datové typy, s nimiž se v dnešním článku seznámíme, jsou asociativní seznamy (alisp) a seznamy vlastností (property list). Asociativní seznamy jsou tvořeny sekvencí tečka dvojic, přičemž první hodnotu v tečka dvojici můžeme považovat za klíč (nemusí být unikátní):

(setq numbers '((one . 1) (two . 2) (three . 3)))

Pro nalezení tečka dvojice slouží funkce assoc a assq (při porovnávání klíče používá funkce eq, takže nebude pracovat korektně pro řetězce!):

(print (assoc 'one numbers))
(print (car (assoc 'one numbers)))
(print (cdr (assoc 'one numbers)))
 
(print (assoc 'two numbers))
(print (car (assoc 'two numbers)))
(print (cdr (assoc 'two numbers)))
 
(print (assq 'two numbers))
(print (car (assq 'two numbers)))
(print (cdr (assq 'two numbers)))
 
(print (assoc 'zero numbers))

Výsledky vyhledání:

(one . 1)
one
1
 
(two . 2)
two
2
 
(two . 2)
two
2
 
nil

Vyhledávat lze i podle hodnoty, tj. na základě druhého prvku v tečka dvojicích:

(print (rassoc 1 numbers))
(print (car (rassoc 1 numbers)))
(print (cdr (rassoc 1 numbers)))

Výsledky:

(one . 1)
one
1

Hodnoty v tečka dvojicích mohou být jakékoli, lze tedy použít i běžná čísla:

(setq rnumbers '((1 . one) (2 . two) (3 . three)))
 
(print (assoc 1 rnumbers))
(print (car (assoc 1 rnumbers)))
(print (cdr (assoc 1 rnumbers)))
 
(print (assoc 2 rnumbers))
(print (car (assoc 2 rnumbers)))
(print (cdr (assoc 2 rnumbers)))
 
(print (assoc 0 rnumbers))
 
(print (rassoc 'two rnumbers))
(print (car (rassoc 'two rnumbers)))
(print (cdr (rassoc 'two rnumbers)))

Výsledky:

(1 . one)
1
one
 
(2 . two)
2
two
 
nil
 
(2 . two)
2
two

Setkat se můžeme i s takzvanými seznamy vlastností (property list). Jedná se o běžné seznamy se sudým počtem prvků, přičemž sudý prvek je jméno vlastnosti a prvek lichý hodnota vlastnosti (indexuje se od 0). Pro přečtení vlastnosti slouží funkce plist-get, pro otestování, zda vlastnost vůbec existuje, pak funkce plist-member (tato druhá funkce existuje pro jednoznačné rozhodnutí o existenci vlastnosti, i když je její hodnota nil):

ict ve školství 24

(setq numbers '(one  1 two  2 three  3))
 
(print (plist-get numbers 'zero))
(print (plist-get numbers 'one))
(print (plist-get numbers 'two))
 
(print (plist-member numbers 'zero))
(print (plist-member numbers 'one))
(print (plist-member numbers 'two))
 
(print "---------------------------------")
 
(setq numbers (plist-put numbers 'zero 0))
(print (plist-get numbers 'zero))

Výsledky:

nil
1
2
nil
(one 1 two 2 three 3)
(two 2 three 3)
"---------------------------------"
0

18. Repositář s demonstračními příklady

Zdrojové kódy většiny dnes popsaných demonstračních příkladů byly uloženy do Git repositáře dostupného na adrese https://github.com/tisnik/elisp-examples (stále na GitHubu :-). V případě, že nebudete chtít klonovat celý repositář (ten je ovšem stále velmi malý, dnes má doslova několik kilobajtů), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:

# Demonstrační příklad Popis Cesta
1 01_sequences.el základní funkce pro zpracování sekvencí https://github.com/tisnik/elisp-examples/blob/master/elisp-5/01_sequences.el
2 02_copy_sequence.el funkce pro kopii sekvence https://github.com/tisnik/elisp-examples/blob/master/elisp-5/02_copy_sequence.el
3 03_vector_constructors.el různé konstruktory vektorů https://github.com/tisnik/elisp-examples/blob/master/elisp-5/03_vector_constructors.el
4 04_vector_predicate.el predikát, zda je hodnota vektorem https://github.com/tisnik/elisp-examples/blob/master/elisp-5/04_vector_predicate.el
5 05_vconcat.el použití funkce vconcat https://github.com/tisnik/elisp-examples/blob/master/elisp-5/05_vconcat.el
6 06_append.el použití funkce append https://github.com/tisnik/elisp-examples/blob/master/elisp-5/06_append.el
7 07_array_predicates.el predikát arrayp https://github.com/tisnik/elisp-examples/blob/master/elisp-5/07_array_predicates.el
8 08_fillarray.el použití funkce fillarray https://github.com/tisnik/elisp-examples/blob/master/elisp-5/08_fillarray.el
9 09_aset_aref.el funkce aset a aref https://github.com/tisnik/elisp-examples/blob/master/elisp-5/09_aset_aref.el
10 10_copy_sequence_again.el kopie sekvencí a potenciální problémy https://github.com/tisnik/elisp-examples/blob/master/elisp-5/10_copy_sequence_again.el
11 11_string_constructors.el konstruktory řetězců https://github.com/tisnik/elisp-examples/blob/master/elisp-5/11_string_constructors.el
12 12_substring.el získání podřetězce https://github.com/tisnik/elisp-examples/blob/master/elisp-5/12_substring.el
13 13_charcodes.el kódy znaků https://github.com/tisnik/elisp-examples/blob/master/elisp-5/13_charcodes.el
14 14_string_match.el regulární výrazy https://github.com/tisnik/elisp-examples/blob/master/elisp-5/14_string_match.el
15 15_string_replace_split.el náhrada znaků, funkce split https://github.com/tisnik/elisp-examples/blob/master/elisp-5/15_string_replace_split.el
16 16_hash_table_constructors.el konstruktory hešovacích tabulek https://github.com/tisnik/elisp-examples/blob/master/elisp-5/16_hash_table_constructors.el
17 17_hash_table_put_items.el přidání prvků do hešovací tabulky https://github.com/tisnik/elisp-examples/blob/master/elisp-5/17_hash_table_put_items.el
18 18_maphash.el funkce maphash https://github.com/tisnik/elisp-examples/blob/master/elisp-5/18_maphash.el
19 19_alist.el asociativní seznamy https://github.com/tisnik/elisp-examples/blob/master/elisp-5/19_alist.el
20 20_plist.el seznamy vlastností https://github.com/tisnik/elisp-examples/blob/master/elisp-5/20_plist.el
Poznámka: všechny popsané demonstrační příklady je možné spustit přímo z příkazové řádky, a to konkrétně následujícím způsobem:
$ emacs -script jméno_skriptu.el

19. Literatura

  1. McCarthy
    „Recursive functions of symbolic expressions and their computation by machine, part I“
    1960
  2. Guy L. Steele
    „History of Scheme“
    2006, Sun Microsystems Laboratories
  3. Kolář J., Muller K.:
    „Speciální programovací jazyky“
    Praha 1981
  4. „AutoLISP Release 9, Programmer's reference“
    Autodesk Ltd., October 1987
  5. „AutoLISP Release 10, Programmer's reference“
    Autodesk Ltd., September 1988
  6. McCarthy, John; Abrahams, Paul W.; Edwards, Daniel J.; Hart, Timothy P.; Levin, Michael I.
    „LISP 1.5 Programmer's Manual“
    MIT Press. ISBN 0 262 130 1 1 4
  7. Carl Hewitt; Peter Bishop and Richard Steiger
    „A Universal Modular Actor Formalism for Artificial Intelligence“
    1973
  8. Feiman, J.
    „The Gartner Programming Language Survey (October 2001)“
    Gartner Advisory
  9. Harold Abelson, Gerald Jay Sussman, Julie Sussman:
    Structure and Interpretation of Computer Programs
    MIT Press. 1985, 1996 (a možná vyšel i další přetisk)
  10. Paul Graham:
    On Lisp
    Prentice Hall, 1993
    Dostupné online na stránce http://www.paulgraham.com/on­lisptext.html
  11. David S. Touretzky
    Common LISP: A Gentle Introduction to Symbolic Computation (Dover Books on Engineering)
  12. Peter Norvig
    Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp
  13. Patrick Winston, Berthold Horn
    Lisp (3rd Edition)
    ISBN-13: 978–0201083194, ISBN-10: 0201083191
  14. Matthias Felleisen, David Van Horn, Dr. Conrad Barski
    Realm of Racket: Learn to Program, One Game at a Time!
    ISBN-13: 978–1593274917, ISBN-10: 1593274912

20. Odkazy na Internetu

  1. Elisp: Sequence: List, Array
    http://ergoemacs.org/emac­s/elisp_list_vs_vector.html
  2. Elisp: Property List
    http://ergoemacs.org/emac­s/elisp_property_list.html
  3. Elisp: Hash Table
    http://ergoemacs.org/emac­s/elisp_hash_table.html
  4. Elisp: Association List
    http://ergoemacs.org/emac­s/elisp_association_list.html
  5. The mapcar Function (An Introduction to Programming in Emacs Lisp)
    https://www.gnu.org/softwa­re/emacs/manual/html_node/e­intr/mapcar.html
  6. Anaphoric macro
    https://en.wikipedia.org/wi­ki/Anaphoric_macro
  7. Some Common Lisp Loop Macro Examples
    https://www.youtube.com/wat­ch?v=3yl8o6r_omw
  8. A Guided Tour of Emacs
    https://www.gnu.org/softwa­re/emacs/tour/
  9. The Roots of Lisp
    http://www.paulgraham.com/ro­otsoflisp.html
  10. Evil (Emacs Wiki)
    https://www.emacswiki.org/emacs/Evil
  11. Evil (na GitHubu)
    https://github.com/emacs-evil/evil
  12. Evil (na stránkách repositáře MELPA)
    https://melpa.org/#/evil
  13. Evil Mode: How I Switched From VIM to Emacs
    https://blog.jakuba.net/2014/06/23/e­vil-mode-how-to-switch-from-vim-to-emacs.html
  14. GNU Emacs (home page)
    https://www.gnu.org/software/emacs/
  15. GNU Emacs (texteditors.org)
    http://texteditors.org/cgi-bin/wiki.pl?GnuEmacs
  16. An Introduction To Using GDB Under Emacs
    http://tedlab.mit.edu/~dr/gdbin­tro.html
  17. An Introduction to Programming in Emacs Lisp
    https://www.gnu.org/softwa­re/emacs/manual/html_node/e­intr/index.html
  18. 27.6 Running Debuggers Under Emacs
    https://www.gnu.org/softwa­re/emacs/manual/html_node/e­macs/Debuggers.html
  19. GdbMode
    http://www.emacswiki.org/e­macs/GdbMode
  20. Emacs (Wikipedia)
    https://en.wikipedia.org/wiki/Emacs
  21. Emacs timeline
    http://www.jwz.org/doc/emacs-timeline.html
  22. Emacs Text Editors Family
    http://texteditors.org/cgi-bin/wiki.pl?EmacsFamily
  23. Vrapper aneb spojení možností Vimu a Eclipse
    https://mojefedora.cz/vrapper-aneb-spojeni-moznosti-vimu-a-eclipse/
  24. Vrapper aneb spojení možností Vimu a Eclipse (část 2: vyhledávání a nahrazování textu)
    https://mojefedora.cz/vrapper-aneb-spojeni-moznosti-vimu-a-eclipse-cast-2-vyhledavani-a-nahrazovani-textu/
  25. Emacs/Evil-mode – A basic reference to using evil mode in Emacs
    http://www.aakarshnair.com/posts/emacs-evil-mode-cheatsheet
  26. From Vim to Emacs+Evil chaotic migration guide
    https://juanjoalvarez.net/es/de­tail/2014/sep/19/vim-emacsevil-chaotic-migration-guide/
  27. Introduction to evil-mode {video)
    https://www.youtube.com/wat­ch?v=PeVQwYUxYEg
  28. EINE (Emacs Wiki)
    http://www.emacswiki.org/emacs/EINE
  29. EINE (Texteditors.org)
    http://texteditors.org/cgi-bin/wiki.pl?EINE
  30. ZWEI (Emacs Wiki)
    http://www.emacswiki.org/emacs/ZWEI
  31. ZWEI (Texteditors.org)
    http://texteditors.org/cgi-bin/wiki.pl?ZWEI
  32. Zmacs (Wikipedia)
    https://en.wikipedia.org/wiki/Zmacs
  33. Zmacs (Texteditors.org)
    http://texteditors.org/cgi-bin/wiki.pl?Zmacs
  34. TecoEmacs (Emacs Wiki)
    http://www.emacswiki.org/e­macs/TecoEmacs
  35. Micro Emacs
    http://www.emacswiki.org/e­macs/MicroEmacs
  36. Micro Emacs (Wikipedia)
    https://en.wikipedia.org/wi­ki/MicroEMACS
  37. EmacsHistory
    http://www.emacswiki.org/e­macs/EmacsHistory
  38. Seznam editorů s ovládáním podobným Emacsu či kompatibilních s příkazy Emacsu
    http://www.finseth.com/emacs.html
  39. evil-numbers
    https://github.com/cofi/evil-numbers
  40. Debuggery a jejich nadstavby v Linuxu (1.část)
    http://fedora.cz/debuggery-a-jejich-nadstavby-v-linuxu/
  41. Debuggery a jejich nadstavby v Linuxu (2.část)
    http://fedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-2-cast/
  42. Debuggery a jejich nadstavby v Linuxu (3): Nemiver
    http://fedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-3-nemiver/
  43. Debuggery a jejich nadstavby v Linuxu (4): KDbg
    http://fedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-4-kdbg/
  44. Debuggery a jejich nadstavby v Linuxu (5): ladění aplikací v editorech Emacs a Vim
    https://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-5-ladeni-aplikaci-v-editorech-emacs-a-vim/
  45. Org mode
    https://orgmode.org/
  46. The Org Manual
    https://orgmode.org/manual/index.html
  47. Kakoune (modální textový editor)
    http://kakoune.org/
  48. Vim-style keybinding in Emacs/Evil-mode
    https://gist.github.com/tro­yp/6b4c9e1c8670200c04c16036805773d8
  49. Emacs – jak začít
    http://www.abclinuxu.cz/clan­ky/navody/emacs-jak-zacit
  50. Programovací jazyk LISP a LISP machines
    https://www.root.cz/clanky/pro­gramovaci-jazyk-lisp-a-lisp-machines/
  51. Evil-surround
    https://github.com/emacs-evil/evil-surround
  52. Spacemacs
    http://spacemacs.org/
  53. Lisp: Common Lisp, Racket, Clojure, Emacs Lisp
    http://hyperpolyglot.org/lisp
  54. Common Lisp, Scheme, Clojure, And Elisp Compared
    http://irreal.org/blog/?p=725
  55. Does Elisp Suck?
    http://irreal.org/blog/?p=675
  56. Emacs pro mírně pokročilé (9): Elisp
    https://www.root.cz/clanky/emacs-elisp/
  57. If I want to learn lisp, are emacs and elisp a good choice?
    https://www.reddit.com/r/e­macs/comments/2m141y/if_i_wan­t_to_learn_lisp_are_emacs_an­d_elisp_a/
  58. Clojure(Script) Interactive Development Environment that Rocks!
    https://github.com/clojure-emacs/cider
  59. An Introduction to Emacs Lisp
    https://harryrschwartz.com/2014/04/08/an-introduction-to-emacs-lisp.html
  60. Emergency Elisp
    http://steve-yegge.blogspot.com/2008/01/emergency-elisp.html
  61. Racket
    https://racket-lang.org/
  62. The Racket Manifesto
    http://felleisen.org/matthi­as/manifesto/
  63. MIT replaces Scheme with Python
    https://www.johndcook.com/blog/2009/03/26/mit-replaces-scheme-with-python/
  64. Adventures in Advanced Symbolic Programming
    http://groups.csail.mit.e­du/mac/users/gjs/6.945/
  65. Why MIT Switched from Scheme to Python (2009)
    https://news.ycombinator.com/i­tem?id=14167453
  66. Starodávná stránka XLispu
    http://www.xlisp.org/
  67. AutoLISP
    https://en.wikipedia.org/wi­ki/AutoLISP
  68. Seriál PicoLisp: minimalistický a výkonný interpret Lispu
    https://www.root.cz/serialy/picolisp-minimalisticky-a-vykonny-interpret-lispu/
  69. Common Lisp
    https://common-lisp.net/
  70. Getting Going with Common Lisp
    https://cliki.net/Getting%20Started
  71. Online Tutorial (Common Lisp)
    https://cliki.net/online%20tutorial
  72. Guile Emacs
    https://www.emacswiki.org/e­macs/GuileEmacs
  73. Guile Emacs History
    https://www.emacswiki.org/e­macs/GuileEmacsHistory
  74. Guile is a programming language
    https://www.gnu.org/software/guile/
  75. MIT Scheme
    http://groups.csail.mit.e­du/mac/projects/scheme/
  76. SIOD: Scheme in One Defun
    http://people.delphiforum­s.com/gjc//siod.html
  77. CommonLispForEmacs
    https://www.emacswiki.org/e­macs/CommonLispForEmacs
  78. Elisp: print, princ, prin1, format, message
    http://ergoemacs.org/emac­s/elisp_printing.html
  79. Special Forms in Lisp
    http://www.nhplace.com/ken­t/Papers/Special-Forms.html
  80. Basic Building Blocks in LISP
    https://www.tutorialspoin­t.com/lisp/lisp_basic_syn­tax.htm
  81. Introduction to LISP – University of Pittsburgh
    https://people.cs.pitt.edu/~mi­los/courses/cs2740/Lectures/Lis­pTutorial.pdf
  82. Why don't people use LISP
    https://forums.freebsd.org/threads/why-dont-people-use-lisp.24572/
  83. Structured program theorem
    https://en.wikipedia.org/wi­ki/Structured_program_the­orem
  84. Clojure: API Documentation
    https://clojure.org/api/api
  85. Tutorial for the Common Lisp Loop Macro
    http://www.ai.sri.com/pkarp/loop.html
  86. Common Lisp's Loop Macro Examples for Beginners
    http://www.unixuser.org/~e­uske/doc/cl/loop.html
  87. A modern list api for Emacs. No 'cl required.
    https://github.com/magnars/dash.el
  88. The LOOP Facility
    http://www.lispworks.com/do­cumentation/HyperSpec/Body/06_a­.htm

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.