Obsah
1. Úpravy Emacsu s Emacs Lisp: možnosti nabízené knihovnou Dash
4. Odstranění konkrétních prvků pomocí -remove-item, -remove-at a -remove-at-indices
5. Odstranění prvních či posledních prvků odpovídajících predikátu ze seznamu
6. Získání podseznamu: take, take-last, drop, drop-last
7. Získání sekvence prvků ze seznamu na základě podmínky: take-while a drop-while
10. Redukce seznamů s akumulací výsledné hodnoty
11. Klasická funkce vyššího řádu -reduce
12. Další formy funkce -reduce
13. Získání mezivýsledků variant funkce -reduce
14. Speciální formy „reducerů“
15. Konstrukce seznamů pomocí -iterate
17. Další formy threading maker
18. Repositář s demonstračními příklady
1. Úpravy Emacsu s Emacs Lisp: možnosti nabízené knihovnou Dash
Velká část pondělního článku byla věnována popisu vlastností makra cl-loop, které bylo do Emacsu přeportováno z Common Lispu. Připomeňme si, že s využitím tohoto makra je možné rozšířit možnosti Emacs Lispu o různé varianty programových smyček, ať již se jedná o smyčky s počitadlem, smyčky určené pro průchod seznamy nebo vektory, smyčky umožňující výpočet a akumulaci hodnot z procházených seznamů (minimum, maximum, suma, …) či dokonce i o různé kombinace těchto typů programových smyček. Makro cl-loop může být užitečné například pro uživatele přecházející na Emacs Lisp z konvenčních programovacích jazyků, ovšem musíme přitom akceptovat fakt, že se v něm používá vlastní DSL (doménově specifický jazyk), který je určitým mixem mezi zápisem používaným v Lispech a zápisem ze strukturovaných jazyků.
Ovšem ve chvíli, kdy se makro cl-loop nebo i standardní speciální forma while používá například pro zpracování prvků v seznamech nebo vektorech, je vhodné se zamyslet nad tím, zda neexistují lepší způsoby řešení těchto úkolů. Existují totiž i alternativní způsoby nabízené například knihovnou pojmenovanou seq; přičemž jedno z možných – a nutno říci, že i velmi elegantních – řešení spočívá ve využití knihovny pojmenované dash. Tato knihovna nabízí makra a funkce vyššího řádu určené pro funkcionálně pojaté zpracování různých typů sekvencí. Jedná se například o filtraci prvků, postupnou redukci sekvencí, aplikaci dalších funkcí na prvky sekvence, rozdělení sekvence podle nějakého kritéria atd.
Většina funkcí, které v knihovně dash nalezneme, nijak nemodifikuje původní (zdrojovou sekvenci), takže jsou tyto operace nedestruktivní. Na druhou stranu je ovšem nutno poznamenat, že seznamy nejsou v Emacs Lispu neměnitelné (immutable), tak, jak je tomu v Clojure, takže je nutné při použití knihovny dash dávat pozor na to, jaké další funkce se volají. Jen pro ukázku si uveďme funkci, která „tiše“ modifikuje původní seznam a je tedy z tohoto pohledu destruktivní:
(setq l '(1 2 3)) (1 2 3) l (1 2 3) (sort l '>) (3 2 1) l (1)
2. Funkce -filter
První funkce, s níž se dnes seznámíme, se při zpracování seznamů používá velmi často. Tato funkce se jmenuje -filter a používá se pro vytvoření nového seznamu, který bude obsahovat jen ty prvky ze seznamu původního, které odpovídají nějakému predikátu (predikát je obecně funkce vracející pro svůj jediný vstup pravdivostní hodnotu). Původní seznam přitom není modifikován, takže je ho později možné použít i pro další účely. Ukažme si použití této funkce. Nejdříve musíme provést import všech potřebných modulů, tj. konkrétně knihovny dash a taktéž knihovny cl-lib, protože v ní najdeme použitelné predikáty:
(package-initialize) (require 'dash) (require 'cl-lib)
Dále vytvoříme vstupní seznam obsahující sekvenci celých čísel od –10 do 10 (včetně obou krajních hodnot), což samozřejmě není nic složitého:
(setq numbers (number-sequence -10 10)) (print numbers) (-10 -9 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8 9 10)
Následně se můžeme pokusit vyfiltrovat všechny sudé hodnoty (důležité je zde slovo vyfiltrovat, nikoli odfiltrovat):
(print (-filter 'cl-evenp numbers)) (-10 -8 -6 -4 -2 0 2 4 6 8 10)
Podobně, pouze výměnou predikátu, získáme liché hodnoty:
(print (-filter 'cl-oddp numbers)) (-9 -7 -5 -3 -1 1 3 5 7 9)
Predikátem může být jakákoli funkce s jedním parametrem, která vrací pravdivostní hodnotu (nebo jinou hodnotu automaticky převedenou na t nebo nil):
(print (-filter (lambda (n) (zerop (% n 3))) numbers)) (-9 -6 -3 0 3 6 9)
Již minule jsme se seznámili s anaforickým makrem –filter umožňujícím zkrácený zápis anonymní funkce – zapisuje se jen její tělo, v němž se použije parametr it:
(print (--filter (zerop (% it 3)) numbers)) (-9 -6 -3 0 3 6 9)
Samozřejmě nám nic nebrání si vytvořit vlastní predikát s využitím pojmenované (neanonymní) funkce a následně tento predikát použít při filtraci:
(defun positive? (n) (> n 0)) (print (-filter 'positive? numbers)) (1 2 3 4 5 6 7 8 9 10)
Složitější predikát pro kladná a současně lichá čísla:
(print (-filter (lambda (n) (and (cl-oddp n) (positive? n))) numbers)) (1 3 5 7 9)
Další složitější predikát pro kladná nebo lichá čísla:
(print (-filter (lambda (n) (or (cl-oddp n) (positive? n))) numbers)) (-9 -7 -5 -3 -1 1 2 3 4 5 6 7 8 9 10)
3. Makro -remove
Výše popsaná funkce -filter sloužila k výběru prvků odpovídajících nějakému predikátu. Přesně opačně se chová funkce pojmenovaná -remove, protože zde predikát slouží k vyloučení prvků. Ve skutečnosti se ovšem (opět) vstupní seznam nemění, prvky jsou „odstraněny“ ze seznamu výsledného.
Ukázku použití této funkce začneme stejně, jako tomu bylo v předchozím příkladu. Import potřebných modulů a vytvoření seznamu se sekvencí celých čísel od –10 do 10 je naprosto shodné s příkladem uvedeným v předchozí kapitole, takže jen krátce:
(package-initialize) (require 'dash) (require 'cl-lib) (setq numbers (number-sequence -10 10)) (print numbers) (-10 -9 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8 9 10)
Vytvoření nového seznamu bez sudých prvků:
(print (-remove 'cl-evenp numbers)) (-9 -7 -5 -3 -1 1 3 5 7 9)
Vytvoření nového seznamu bez lichých prvků:
(print (-remove 'cl-oddp numbers)) (-10 -8 -6 -4 -2 0 2 4 6 8 10)
Vlastní predikát pro ignorování prvků dělitelných třemi:
(print (-remove (lambda (n) (zerop (% n 3))) numbers)) (-10 -8 -7 -5 -4 -2 -1 1 2 4 5 7 8 10)
Dtto, ovšem nyní s využitím anaforického makra –remove:
(print (--remove (zerop (% it 3)) numbers)) (-10 -8 -7 -5 -4 -2 -1 1 2 4 5 7 8 10)
Další varianta, tentokrát s vlastním predikátem:
(defun positive? (n) (> n 0)) (print (-remove 'positive? numbers)) (-10 -9 -8 -7 -6 -5 -4 -3 -2 -1 0)
Složitější predikát pro kladná a současně lichá čísla (ta jsou odstraněna):
(print (-remove (lambda (n) (and (cl-oddp n) (positive? n))) numbers)) (-10 -9 -8 -7 -6 -5 -4 -3 -2 -1 0 2 4 6 8 10)
Další složitější predikát pro kladná nebo lichá čísla (ta jsou odstraněna):
(print (-remove (lambda (n) (or (cl-oddp n) (positive? n))) numbers)) (-10 -8 -6 -4 -2 0)
4. Odstranění konkrétních prvků pomocí -remove-item, -remove-at a -remove-at-indices
Poměrně užitečné jsou i další varianty funkce -remove. Význam těchto variant je vypsán v následující tabulce:
Funkce | Význam |
---|---|
-remove-item | vrátí nový seznam, v němž se nebudou nacházet prvky specifikované hodnoty |
-remove-at | vrátí nový seznam, v němž se nebude nacházet prvek, který byl původně na pozici n |
-remove-at-indices | vrátí nový seznam, v němž se nebudou nacházet prvky umístěné na pozicích specifikovaných v prvním seznamu |
Samozřejmě si opět ukážeme, jak se tyto funkce používají. Začneme již klasicky – prostou sekvencí celých čísel od –10 do 10:
(package-initialize) (require 'dash) (require 'cl-lib) (setq numbers (number-sequence -10 10)) (print numbers) (-10 -9 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8 9 10)
Vytvoření nového seznamu, v němž nebudou prvky s hodnotou 0:
(print (-remove-item 0 numbers)) (-10 -9 -8 -7 -6 -5 -4 -3 -2 -1 1 2 3 4 5 6 7 8 9 10)
Vytvoření nového seznamu, v němž nebude prvek s hodnotou 42. Takový prvek neexistuje, takže získáme kopii seznamu původního:
(print (-remove-item 42 numbers)) (-10 -9 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8 9 10)
Pro další testy použijeme seznam s devíti čísly:
(setq numbers '(0 0 0 1 1 1 2 2 2)) (print numbers) (0 0 0 1 1 1 2 2 2)
Vytvoření nového seznamu, v němž nebudou prvky s hodnotou 0:
(print (-remove-item 0 numbers)) (1 1 1 2 2 2)
Vytvoření nového seznamu, v němž nebudou prvky s hodnotou 1:
(print (-remove-item 1 numbers)) (0 0 0 2 2 2)
Vytvoření nového seznamu, v němž nebudou prvky s hodnotou 2:
(print (-remove-item 2 numbers)) (0 0 0 1 1 1)
Vytvoření seznamu bez prvku na první pozici:
(print (-remove-at 0 numbers)) (-9 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8 9 10)
Vytvoření seznamu bez prvku na druhé pozici:
(print (-remove-at 1 numbers)) (-10 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8 9 10)
Vytvoření seznamu bez prvku na desáté pozici:
(print (-remove-at 10 numbers)) (-10 -9 -8 -7 -6 -5 -4 -3 -2 -1 1 2 3 4 5 6 7 8 9 10)
Odstranění prvku s indexem 0:
(print (-remove-at-indices '(0) numbers)) (-9 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8 9 10)
Odstranění prvků s indexy 0, 1 a 2:
(print (-remove-at-indices '(0 1 2) numbers)) (-7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8 9 10)
Odstranění prvků s indexy 0, 10 a 20:
(print (-remove-at-indices '(10 20 0) numbers)) (-9 -8 -7 -6 -5 -4 -3 -2 -1 1 2 3 4 5 6 7 8 9)
5. Odstranění prvních či posledních prvků odpovídajících predikátu ze seznamu
Další dvě užitečné funkce se jmenují -remove-first a -remove-last. Tyto funkce slouží k odstranění prvního resp. posledního prvku, který odpovídá nějakému predikátu. Tyto funkce sice vypadají nenápadně, ale díky nim lze z programového kódu odstranit poměrně velké množství složitějších programových smyček. Inicializace příkladu je shodná s příklady předchozími (import modulů, vytvoření sekvence celých čísel), takže si ji již nebudeme znovu uvádět. Původní číselná sekvence vypadá následovně:
(print numbers) (-10 -9 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8 9 10)
Ukažme si vytvoření nového seznamu bez prvního prvku, který je sudým číslem. Čistě náhodou se jedná o zcela první prvek seznamu (car):
(print (-remove-first 'cl-evenp numbers)) (-9 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8 9 10)
Vytvoření nového seznamu bez prvního prvku, který je lichým číslem. V našem konkrétním případě se jedná o prvek s hodnotou –9, takže ta ve výsledném seznamu chybí:
(print (-remove-first 'cl-oddp numbers)) (-10 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8 9 10)
Samozřejmě je možné si vyzkoušet i složitější příklad, například odstranění prvního prvku beze zbytku dělitelného třemi:
(print (-remove-first (lambda (n) (zerop (% n 3))) numbers)) (-10 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8 9 10)
Dtto, ovšem s využitím formy, v níž není nutné explicitně zapisovat formu lambda:
(print (--remove-first (zerop (% it 3)) numbers)) (-10 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8 9 10)
Odstranění prvního záporného prvku (-10):
(print (--remove-first (> 0 it) numbers)) (-9 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8 9 10)
Odstranění prvního kladného prvku (jedničky):
(print (--remove-first (< 0 it) numbers)) (-10 -9 -8 -7 -6 -5 -4 -3 -2 -1 0 2 3 4 5 6 7 8 9 10)
Další příklady již ukazují funkci -remove-last:
(print numbers) (-10 -9 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8 9 10)
Odstranění posledního sudého čísla (10):
(print (-remove-last 'cl-evenp numbers)) (-10 -9 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8 9)
Odstranění posledního lichého čísla (9):
(print (-remove-last 'cl-oddp numbers)) (-10 -9 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8 10)
Odstranění posledního čísla dělitelného třemi (9):
(print (-remove-last (lambda (n) (zerop (% n 3))) numbers)) (-10 -9 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8 10)
Odstranění posledního čísla dělitelného třemi (9), podoba s anaforickým makrem:
(print (--remove-last (zerop (% it 3)) numbers)) (-10 -9 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8 10)
Odstranění posledního záporného prvku (-1):
(print (--remove-last (> 0 it) numbers)) (-10 -9 -8 -7 -6 -5 -4 -3 -2 0 1 2 3 4 5 6 7 8 9 10)
Odstranění posledního kladného prvku (10):
(print (--remove-last (< 0 it) numbers)) (-10 -9 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8 9)
6. Získání podseznamu: take, take-last, drop, drop-last
Ve chvíli, kdy budeme potřebovat získat prvních n či posledních n prvků ze vstupního seznamu, použijeme funkce take a take-last. Vstupem nyní bude seznam obsahující celočíselné hodnoty 0 až 20:
(setq numbers (number-sequence 0 20)) (print numbers) (0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20)
Získání prvních pěti prvků:
(print (-take 5 numbers)) (0 1 2 3 4)
Získání prvních 100 prvků (nedojde k chybě, vrátí se celý kratší seznam):
(print (-take 100 numbers)) (0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20)
Získání posledních pěti prvků:
(print (-take-last 5 numbers)) (16 17 18 19 20)
Získání posledních 100 prvků (taktéž nedojde k chybě, vrátí se celý kratší seznam):
(print (-take-last 100 numbers)) (0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20)
Opakem jsou funkce -drop a -drop-last vracející původní seznam bez prvních či posledních n prvků. Vstupem bude stejný seznam jako v předchozích příkladech:
(setq numbers (number-sequence 0 20)) (print numbers) (0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20)
Kopie seznamu bez prvních pěti prvků:
(print (-drop 5 numbers)) (5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20)
Zajímavá situace – zde se vrátí prázdný seznam (odstraněny jsou všechny prvky):
(print (-drop 100 numbers)) nil
Kopie seznamu bez posledních pěti prvků:
(print (-drop-last 5 numbers)) (0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15)
Podobná situace, jako v příkladu s -drop 100:
(print (-drop-last 100 numbers)) nil
7. Získání sekvence prvků ze seznamu na základě podmínky: take-while a drop-while
I další dvojice funkcí nazvaných -take-while a -drop-while je v praxi velmi užitečná, protože díky nim můžeme nahradit další typ potenciálně složitějších programových smyček. Funkce -take-while prochází seznamem a pokud platí podmínka (predikát), tvoří z jednotlivých prvků seznam výstupní. Jakmile podmínka platit přestane, je funkce ukončena a celý doposud vytvořený seznam se vrátí. Naproti tomu se u funkce -drop-while postupuje pochopitelně obráceně: najde se první prvek, pro nějž je predikát nepravdivý a následně se vrátí seznam obsahující zbytek seznamu (od tohoto prvku do konce).
V příkladech se vrátíme k původní sekvenci celých čísel od –10 do 10:
(setq numbers (number-sequence -10 10)) (print numbers) (-10 -9 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8 9 10)
Získání prvních prvků menších než 0, tato hodnota způsobí ukončení generování seznamu:
(print (-take-while (lambda (n) (< n 0)) numbers)) (-10 -9 -8 -7 -6 -5 -4 -3 -2 -1)
Dtto, ovšem nyní s využitím anaforického makra –take-while:
(print (--take-while (< it 0) numbers)) (-10 -9 -8 -7 -6 -5 -4 -3 -2 -1)
V dalším příkladu již první prvek neodpovídá podmínce, takže výsledkem je prázdný seznam, který je v Emacs Lispu totožný s nil:
(print (--take-while (> it 0) numbers)) nil
Predikát t je vždy pravdivý a získáme tak kopii seznamu:
(print (--take-while t numbers)) (-10 -9 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8 9 10)
Predikát přestane být splněn pro prvek 0, takže se vrátí seznam od tohoto prvku do konce:
(print (-drop-while (lambda (n) (< n 0)) numbers)) (0 1 2 3 4 5 6 7 8 9 10)
Dtto, ovšem nyní s využitím anaforického makra –drop-while:
(print (--drop-while (< it 0) numbers)) (0 1 2 3 4 5 6 7 8 9 10)
Zde není predikát splněn ani pro první prvek – vrátí se kopie seznamu:
(print (--drop-while (> it 0) numbers)) (-10 -9 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8 9 10)
Predikát t je vždy pravdivý a získáme tak prázdný seznam (všechny prvky jsou odstraněny):
(print (--drop-while t numbers)) nil
Opak předchozího predikátu – získáme zde kopii původního seznamu:
(print (--drop-while nil numbers)) (-10 -9 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8 9 10)
8. Funkce -concat a -flatten
Funkce -concat se používá pro spojení většího množství seznamů, přičemž prvky seznamů jsou na stejné úrovni, na rozdíl od standardní formy cons. Funkci si otestujeme na třech seznamech:
(setq l1 '(1 2 3)) (setq l2 '(3 4 5)) (setq l3 '(7 8 9))
Výsledek by měl vypadat následovně:
(print (-concat l1 l2 l3)) (1 2 3 3 4 5 7 8 9)
Pokud seznamy obsahují vnořené podseznamy, je tato struktura ve výsledku zachována:
(setq l1 '(1 (2 3))) (setq l2 '((3 4) 5)) (setq l3 '(7 (8) 9))
Výsledek spojení těchto tří seznamů:
(print (-concat l1 l2 l3)) (1 (2 3) (3 4) 5 7 (8) 9)
Funkce -flatten vrátí původní seznam, ovšem ve „zploštělé“ podobě, v níž budou prvky všech podseznamů postupně vložené do jednorozměrného lineárně vázaného seznamu. Význam této funkce je patrný z následující trojice příkladů:
(print (-flatten '(1 2 3 4 5))) (1 2 3 4 5)
(print (-flatten '(1 (2 3) (4 5)))) (1 2 3 4 5)
(print (-flatten '(1 (2 (3 (4 (5))))))) (1 2 3 4 5)
9. Funkce -flatten-n
Zajímavější je funkce pojmenovaná -flatten-n, která taktéž konvertuje vstupní datovou strukturu do její „zploštělé“ podoby, ovšem jen do specifikované úrovně. Pro již zploštělý seznam získáme jeho kopii:
(dotimes (n 3) (print (-flatten-n n '(1 2 3 4 5)))) (1 2 3 4 5) (1 2 3 4 5) (1 2 3 4 5)
Pro datovou strukturu s pouhými dvěma úrovněmi rekurze je chování následující:
(dotimes (n 3) (print (-flatten-n n '(1 (2 3) (4 5))))) (1 (2 3) (4 5)) (1 2 3 4 5) (1 2 3 4 5)
A konečně si ukažme chování na datové struktuře s pěti rekurzivními úrovněmi:
(dotimes (n 7) (print (-flatten-n n '(1 (2 (3 (4 (5)))))))) (1 (2 (3 (4 (5))))) (1 2 (3 (4 (5)))) (1 2 3 (4 (5))) (1 2 3 4 (5)) (1 2 3 4 5) (1 2 3 4 5) (1 2 3 4 5)
10. Redukce seznamů s akumulací výsledné hodnoty
Další velmi užitečnou funkcí vyššího řádu je funkce nazvaná -reduce a její různé varianty. Při použití těchto funkcí dochází k postupné redukci prvků uložených ve vstupní sekvenci, a to (postupnou) aplikací zvolené uživatelské funkce na jednotlivé prvky a po krocích počítaný mezivýsledek, jenž se většinou nazývá akumulátor. Příklad výpočtu faktoriálu deseti postupnou redukcí seznamu (1 2 3 4 5 6 7 8 9 10):
(-reduce (lambda (n acc) (* n acc)) (number-sequence 1 10)) 3628800
Ve skutečnosti existuje několik podobně koncipovaných funkcí, které se liší například tím, zda je seznam zpracovávána od svého začátku nebo od konce. K dispozici jsou i funkce vracející všechny postupně tvořené mezivýsledky.
11. Klasická funkce vyššího řádu -reduce
Základní formou „reduceru“ je funkce nazvaná jednoduše -reduce. Její možnosti si vyzkoušíme na nám již dobře známé sekvenci celých čísel, tentokrát v rozsahu 0 až 10 (což se bude dobře počítat):
(setq numbers (number-sequence 0 10)) (print numbers) (0 1 2 3 4 5 6 7 8 9 10)
Pokud této funkci vyššího řádu předáme funkci +, bude postupně aplikována na mezivýsledek a každý prvek seznamu. Prvním mezivýsledkem je první prvek vstupního seznamu (tedy v našem případě nula):
(print (-reduce '+ numbers)) 55
Stejného výsledku, i když s větší námahou, dosáhneme použitím anonymní funkce. Této funkci se předává jak mezivýsledek (akumulátor), tak i n-tý prvek seznamu; seznamem se prochází od začátku:
(print (-reduce (lambda (acc n) (+ acc n)) numbers)) 55
Funkce -reduce existuje i ve variantě anaforického makra umožňujícího zkrácený zápis (preferovaná forma, v níž jsou acc a it povinná jména):
(print (--reduce (+ acc it) numbers)) 55
Relativně snadno můžeme akumulací získat prvek s minimální hodnotou:
(print (--reduce (min acc it) numbers)) 0
Popř. prvek s hodnotou maximální:
(print (--reduce (max acc it) numbers)) 10
12. Další formy funkce -reduce
Existují i další formy funkce -reduce. Relativně často se můžeme setkat s použitím funkce nazvaná -reduce-r, která seznam taktéž postupně redukuje, ale od konce. Pozor si musíme dát na to, že je prohozeno i pořadí argumentů předávaných do redukční funkce, což nás ovšem nemusí zajímat ve chvíli, kdy použijeme anaforické makro. Opět začněme sekvencí celých čísel:
(setq numbers (number-sequence 0 10)) (print numbers) (0 1 2 3 4 5 6 7 8 9 10)
U součtu (dvou prvků) je jedno, z jaké strany je seznam zpracováván:
(print (-reduce-r '+ numbers)) 55
Použití explicitně zapsané anonymní funkce:
(print (-reduce-r (lambda (n acc) (+ acc n)) numbers)) 55
Samozřejmě můžeme použít anaforické makro a nezajímat se o pořadí předávaných argumentů:
(print (--reduce-r (+ acc it) numbers)) 55
Postupné zjištění prvku s minimální hodnotou:
(print (--reduce-r (min acc it) numbers)) 0
Postupné zjištění prvku s maximální hodnotou:
(print (--reduce-r (max acc it) numbers)) 10
Druhá mocnina prvku, jehož čtverec má maximální hodnotu:
(print (--reduce (max acc (* it it)) numbers)) 100
Pokud seznamem procházíme odzadu, získáme odlišný výsledek (poslední prvek se přímo stane počáteční hodnotou akumulátoru):
(print (--reduce-r (max acc (* it it)) numbers)) 81
Podívejme se nyní na viditelnější rozdíl mezi -reduce a -reduce-r. Vytvoříme si seznam s řetězci „0“, „1“ až „10“:
(setq numbers (->> (number-sequence 0 10) (--map (format "%s" it))))
Test, jak seznam skutečně vypadá:
(print numbers) ("0" "1" "2" "3" "4" "5" "6" "7" "8" "9" "10")
Nyní pomocí concat a –reduce vytvoříme nový řetězec s ciframi:
(print (--reduce (concat acc "-" it) numbers)) "0-1-2-3-4-5-6-7-8-9-10"
Pro –reduce-r samozřejmě získáme řetězec, kde jsou čísla v sestupné řadě:
(print (--reduce-r (concat acc "-" it) numbers)) "10-9-8-7-6-5-4-3-2-1-0"
Další formou je funkce –reduce-from, u níž se explicitně stanoví počáteční hodnota akumulátoru (nepůjde tedy o první prvek):
(print (--reduce-from (concat acc "-" it) "START" numbers)) "START-0-1-2-3-4-5-6-7-8-9-10"
Samozřejmě můžeme zkombinovat vlastnosti -reduce-r a -reduce-from realizované ve funkci -reduce-r-from a v anaforickém makru –reduce-r-from:
(print (--reduce-r-from (concat acc "-" it) "START" numbers) "START-10-9-8-7-6-5-4-3-2-1-0"
13. Získání mezivýsledků variant funkce -reduce
V některých situacích se nám mohou hodit i mezivýsledky, které jsou postupně funkcemi -reduce nebo -reduce-r získávány. K tomu slouží další funkce nazvané -reductions, -reductions-r, -reductions-from a konečně -reductions-r-from. Chování těchto funkcí bude ukázáno na dalších příkladech; základem bude (opět) sekvence řetězců „0“, „1“, „2“ atd.:
(setq numbers (->> (number-sequence 0 10) (--map (format "%s" it))))
Test, jak seznam skutečně vypadá:
(print numbers) ("0" "1" "2" "3" "4" "5" "6" "7" "8" "9" "10")
Dále si vytvoříme pomocnou funkci nazvanou cc, která získá hodnotu akumulátoru a n-tého prvku. Výsledkem bude řetězec získaný spojením obou hodnot znakem „-“:
(defun cc (acc it) (concat acc "-" it))
Dtto pro varianty „-r“:
(defun ccr (it acc) (concat acc "-" it))
Řetězce získávané postupným voláním -reduce:
(print (-reductions 'cc numbers)) ("0" "0-1" "0-1-2" "0-1-2-3" "0-1-2-3-4" "0-1-2-3-4-5" "0-1-2-3-4-5-6" "0-1-2-3-4-5-6-7" "0-1-2-3-4-5-6-7-8" "0-1-2-3-4-5-6-7-8-9" "0-1-2-3-4-5-6-7-8-9-10")
Řetězce získávané postupným voláním -reduce-r (pozor na pořadí, zde se vrací prvky tak, jak se rozbaluje rekurzivní zanoření):
(print (-reductions-r 'ccr numbers)) ("10-9-8-7-6-5-4-3-2-1-0" "10-9-8-7-6-5-4-3-2-1" "10-9-8-7-6-5-4-3-2" "10-9-8-7-6-5-4-3" "10-9-8-7-6-5-4" "10-9-8-7-6-5" "10-9-8-7-6" "10-9-8-7" "10-9-8" "10-9" "10")
Varianta s „-from“, na které je patrné, že se skutečně nezačíná prvním prvkem seznamu, ale specifikovanou hodnotou:
(print (-reductions-from 'cc "START" numbers)) ("START" "START-0" "START-0-1" "START-0-1-2" "START-0-1-2-3" "START-0-1-2-3-4" "START-0-1-2-3-4-5" "START-0-1-2-3-4-5-6" "START-0-1-2-3-4-5-6-7" "START-0-1-2-3-4-5-6-7-8" "START-0-1-2-3-4-5-6-7-8-9" "START-0-1-2-3-4-5-6-7-8-9-10")
Kombinace předchozích dvou příkladů:
(print (-reductions-r-from 'ccr "START" numbers)) ("START-10-9-8-7-6-5-4-3-2-1-0" "START-10-9-8-7-6-5-4-3-2-1" "START-10-9-8-7-6-5-4-3-2" "START-10-9-8-7-6-5-4-3" "START-10-9-8-7-6-5-4" "START-10-9-8-7-6-5" "START-10-9-8-7-6" "START-10-9-8-7" "START-10-9-8" "START-10-9" "START-10" "START")
14. Speciální formy „reducerů“
V knihovně dash nalezneme i speciální formy reducerů, které slouží ke specifickým účelům. I tyto formy si ukážeme na několika příkladech. Vstupem bude nám již známý seznam s celými čísly, tentokrát začneme od jedničky:
(setq numbers (number-sequence 1 10)) (print numbers) (1 2 3 4 5 6 7 8 9 10)
Reducer pro výpočet sumy všech prvků nemusíme složitě implementovat, protože již existuje:
(print (-sum numbers)) 55
Podobně lze získat součin všech prvků (proto hodnoty začínaly od jedničky a nikoli od nuly):
(print (-product numbers)) 3628800
Nalezení prvku s minimální hodnotou:
(print (-min numbers)) 1
Nalezení prvku s maximální hodnotou:
(print (-max numbers)) 10
Další příklady ukážou použití reducerů -min-by a -max-by, které také hledají minimální a maximální prvky, ovšem s využitím uživatelsky definovaného komparátoru.
Vstup:
(setq words '("Lorem" "ipsum" "dolor" "sit" "amet", "consectetur" "adipiscing" "elit", "sed" "do" "eiusmod" "tempor")) (print words) ("Lorem" "ipsum" "dolor" "sit" "amet" (\, "consectetur") "adipiscing" "elit" (\, "sed") "do" "eiusmod" "tempor")
Nalezení nejkratšího slova:
(print (--min-by (> (length it) (length other)) words)) "do"
Nalezení nejdelšího slova:
(print (--max-by (> (length it) (length other)) words)) "adipiscing"
15. Konstrukce seznamů pomocí -iterate
Velmi užitečnou funkcí je funkce -iterate, která slouží ke konstrukci seznamů obsahujících hodnoty vygenerované programově. Funkci -iterace se předávají tři parametry: takzvaná generující funkce, počáteční hodnota a celkový počet hodnot. Výsledný seznam obsahuje ve svém prvním prvku počáteční hodnotu. Generující funkce je nejprve volána s touto počáteční hodnotou a výsledek je opět vložen do generovaného seznamu. Potom je tatáž funkce opět volána, ovšem je jí předána dříve vygenerovaná hodnota atd. atd. Tímto způsobem lze programově vytvořit například některé aritmetické či geometrické řady apod.
Generující funkce vrací svůj vstup, výsledkem bude řada stejných čísel:
(print (-iterate (lambda (it) it) 0 10)) (0 0 0 0 0 0 0 0 0 0)
Stejný příklad, ovšem používající anaforické makro:
(print (--iterate it 0 10)) (0 0 0 0 0 0 0 0 0 0)
Generující funkce vrací původní hodnotu zvýšenou o jedničku; výsledkem je aritmetická řada:
(print (-iterate (lambda (it) (+ 1 it)) 0 10)) (0 1 2 3 4 5 6 7 8 9)
Stejný příklad, ovšem používající anaforické makro:
(print (--iterate (+ 1 it) 0 10)) (0 1 2 3 4 5 6 7 8 9)
Vytvoření řady s mocninami dvojky (musí se začínat hodnotou 1, ne 0):
(print (-iterate (lambda (it) (* 2 it)) 1 10)) (1 2 4 8 16 32 64 128 256 512)
Stejný příklad, ovšem používající anaforické makro:
(print (--iterate (* 2 it) 1 10)) (1 2 4 8 16 32 64 128 256 512)
Exponenciální řada s přetečením výsledků :-)
(print (-iterate (lambda (it) (* it it)) 2 10)) (2 4 16 256 65536 4294967296 0 0 0 0)
Stejný příklad, ovšem opět používající anaforické makro:
(print (--iterate (* it it) 2 10)) (2 4 16 256 65536 4294967296 0 0 0 0)
16. Threading makra podruhé
O takzvaných threading makrech jsme se ve stručnosti zmínili již v předchozím článku, ovšem jedná se o tak užitečná makra, že si zaslouží poněkud delší popis a uvedení většího množství demonstračních příkladů. Víme již, že základní threading makro zapisované znaky → se používá ve chvíli, kdy potřebujeme nahradit relativně málo čitelný zápis:
(a (b (c x)))
za zápis:
(-> x c b a)
který můžeme číst „hodnotu x předej funkci c, návratovou hodnotu funkce c předej do funkce b a konečně návratovou hodnotu funkce b předej do funkce a. Výsledná hodnota této funkce bude současně i výsledkem celého threading makra“
Ve skutečnosti nejsme nijak omezeni celkovým počtem funkcí, které jsou do makra předány; jediné omezení spočívá v tom, že výsledek předchozí funkce se předá do funkce další na prvním místě. Podívejme se na jednoduchý příklad, v němž jsou použity pouze funkce s jedním vstupem (a samozřejmě s jedním výstupem):
(print (-> '(1 (2 3) 4) -flatten -sum -))
Předchozí forma je čitelnější, než původní „lispovský“ zápis:
(print (- (-sum (-flatten '(1 (2 3) 4)))))
Funkce mohou mít i vedlejší efekt, protože vše, co threading makro provádí, je „pouhá“ reorganizace zdrojového kódu.
(-> (number-sequence 0 30) length print)
S výsledkem:
31
Popř. poněkud složitější příklad, v němž funkci + předáme jako první parametr výsledek volání funkce length:
(-> (number-sequence 0 30) length (+ 1) print)
S výsledkem:
32
Na předchozím příkladu je nejdůležitější pochopit význam třetího řádku:
(+ 1)
Ve skutečnosti totiž threading makro předá výsledek předchozí funkce (ať je jakýkoli) ihned za volání +, takže se ve skutečnosti provede:
(+ previous_result 1)
Zkusme si ještě nepatrně složitější příklad s funkcemi akceptujícími větší počet parametrů:
(-> '(1 (2 (3 (4 5)))) -flatten -sum (+ 1) (* 2 2) print)
17. Další formy threading maker
U mnoha výše popsaných funkcí a maker se seznam předává na posledním místě (poslední parametr). Proto většinou není možné použít výše uvedené threading makro ->, ale makro nazvané ->>:
(->> '(1 2 3 4 5 6 7 8) (--filter (< it 5)) -sum print)
popř.:
(->> '(1 2 3 4 5 6 7 8) (--filter (< it 5)) (--map (* 2 it)) -sum print)
V posledním (nejsložitějším) příkladu je použito makro -->, které umožňuje postupné probublávání mezivýsledků, podobně, jako je tomu u maker předchozích. Ovšem mezivýsledky jsou vkládány na místo, kde je použit symbol it. Pozor ovšem na to, že v následujícím příkladu má it dva významy a threading makru patří pouze zvýrazněné symboly:
(--> '(1 2 3 4 5 6 7 8) (--map (format "%s" it) it) (--reduce (concat acc "-" it) it) (concat "*" it "*") print)
Výsledek:
"*1-2-3-4-5-6-7-8*"
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:
19. Literatura
- Tutorial for the Common Lisp Loop Macro
http://www.ai.sri.com/pkarp/loop.html - Common Lisp's Loop Macro Examples for Beginners
http://www.unixuser.org/~euske/doc/cl/loop.html - A modern list api for Emacs. No 'cl required.
https://github.com/magnars/dash.el - The LOOP Facility
http://www.lispworks.com/documentation/HyperSpec/Body/06_a.htm - McCarthy
„Recursive functions of symbolic expressions and their computation by machine, part I“
1960 - Guy L. Steele
„History of Scheme“
2006, Sun Microsystems Laboratories - Kolář J., Muller K.:
„Speciální programovací jazyky“
Praha 1981 - „AutoLISP Release 9, Programmer's reference“
Autodesk Ltd., October 1987 - „AutoLISP Release 10, Programmer's reference“
Autodesk Ltd., September 1988 - 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 - Carl Hewitt; Peter Bishop and Richard Steiger
„A Universal Modular Actor Formalism for Artificial Intelligence“
1973 - Feiman, J.
„The Gartner Programming Language Survey (October 2001)“
Gartner Advisory - 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) - Paul Graham:
On Lisp
Prentice Hall, 1993
Dostupné online na stránce http://www.paulgraham.com/onlisptext.html - David S. Touretzky
Common LISP: A Gentle Introduction to Symbolic Computation (Dover Books on Engineering)
- Peter Norvig
Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp
20. Odkazy na Internetu
- The mapcar Function (An Introduction to Programming in Emacs Lisp)
https://www.gnu.org/software/emacs/manual/html_node/eintr/mapcar.html - Anaphoric macro
https://en.wikipedia.org/wiki/Anaphoric_macro - Some Common Lisp Loop Macro Examples
https://www.youtube.com/watch?v=3yl8o6r_omw - A Guided Tour of Emacs
https://www.gnu.org/software/emacs/tour/ - The Roots of Lisp
http://www.paulgraham.com/rootsoflisp.html - Evil (Emacs Wiki)
https://www.emacswiki.org/emacs/Evil - Evil (na GitHubu)
https://github.com/emacs-evil/evil - Evil (na stránkách repositáře MELPA)
https://melpa.org/#/evil - Evil Mode: How I Switched From VIM to Emacs
https://blog.jakuba.net/2014/06/23/evil-mode-how-to-switch-from-vim-to-emacs.html - GNU Emacs (home page)
https://www.gnu.org/software/emacs/ - GNU Emacs (texteditors.org)
http://texteditors.org/cgi-bin/wiki.pl?GnuEmacs - An Introduction To Using GDB Under Emacs
http://tedlab.mit.edu/~dr/gdbintro.html - An Introduction to Programming in Emacs Lisp
https://www.gnu.org/software/emacs/manual/html_node/eintr/index.html - 27.6 Running Debuggers Under Emacs
https://www.gnu.org/software/emacs/manual/html_node/emacs/Debuggers.html - GdbMode
http://www.emacswiki.org/emacs/GdbMode - Emacs (Wikipedia)
https://en.wikipedia.org/wiki/Emacs - Emacs timeline
http://www.jwz.org/doc/emacs-timeline.html - Emacs Text Editors Family
http://texteditors.org/cgi-bin/wiki.pl?EmacsFamily - Vrapper aneb spojení možností Vimu a Eclipse
https://mojefedora.cz/vrapper-aneb-spojeni-moznosti-vimu-a-eclipse/ - 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/ - Emacs/Evil-mode – A basic reference to using evil mode in Emacs
http://www.aakarshnair.com/posts/emacs-evil-mode-cheatsheet - From Vim to Emacs+Evil chaotic migration guide
https://juanjoalvarez.net/es/detail/2014/sep/19/vim-emacsevil-chaotic-migration-guide/ - Introduction to evil-mode {video)
https://www.youtube.com/watch?v=PeVQwYUxYEg - EINE (Emacs Wiki)
http://www.emacswiki.org/emacs/EINE - EINE (Texteditors.org)
http://texteditors.org/cgi-bin/wiki.pl?EINE - ZWEI (Emacs Wiki)
http://www.emacswiki.org/emacs/ZWEI - ZWEI (Texteditors.org)
http://texteditors.org/cgi-bin/wiki.pl?ZWEI - Zmacs (Wikipedia)
https://en.wikipedia.org/wiki/Zmacs - Zmacs (Texteditors.org)
http://texteditors.org/cgi-bin/wiki.pl?Zmacs - TecoEmacs (Emacs Wiki)
http://www.emacswiki.org/emacs/TecoEmacs - Micro Emacs
http://www.emacswiki.org/emacs/MicroEmacs - Micro Emacs (Wikipedia)
https://en.wikipedia.org/wiki/MicroEMACS - EmacsHistory
http://www.emacswiki.org/emacs/EmacsHistory - Seznam editorů s ovládáním podobným Emacsu či kompatibilních s příkazy Emacsu
http://www.finseth.com/emacs.html - evil-numbers
https://github.com/cofi/evil-numbers - Debuggery a jejich nadstavby v Linuxu (1.část)
http://fedora.cz/debuggery-a-jejich-nadstavby-v-linuxu/ - Debuggery a jejich nadstavby v Linuxu (2.část)
http://fedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-2-cast/ - Debuggery a jejich nadstavby v Linuxu (3): Nemiver
http://fedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-3-nemiver/ - Debuggery a jejich nadstavby v Linuxu (4): KDbg
http://fedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-4-kdbg/ - 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/ - Org mode
https://orgmode.org/ - The Org Manual
https://orgmode.org/manual/index.html - Kakoune (modální textový editor)
http://kakoune.org/ - Vim-style keybinding in Emacs/Evil-mode
https://gist.github.com/troyp/6b4c9e1c8670200c04c16036805773d8 - Emacs – jak začít
http://www.abclinuxu.cz/clanky/navody/emacs-jak-zacit - Programovací jazyk LISP a LISP machines
https://www.root.cz/clanky/programovaci-jazyk-lisp-a-lisp-machines/ - Evil-surround
https://github.com/emacs-evil/evil-surround - Spacemacs
http://spacemacs.org/ - Lisp: Common Lisp, Racket, Clojure, Emacs Lisp
http://hyperpolyglot.org/lisp - Common Lisp, Scheme, Clojure, And Elisp Compared
http://irreal.org/blog/?p=725 - Does Elisp Suck?
http://irreal.org/blog/?p=675 - Emacs pro mírně pokročilé (9): Elisp
https://www.root.cz/clanky/emacs-elisp/ - If I want to learn lisp, are emacs and elisp a good choice?
https://www.reddit.com/r/emacs/comments/2m141y/if_i_want_to_learn_lisp_are_emacs_and_elisp_a/ - Clojure(Script) Interactive Development Environment that Rocks!
https://github.com/clojure-emacs/cider - An Introduction to Emacs Lisp
https://harryrschwartz.com/2014/04/08/an-introduction-to-emacs-lisp.html - Emergency Elisp
http://steve-yegge.blogspot.com/2008/01/emergency-elisp.html - Racket
https://racket-lang.org/ - The Racket Manifesto
http://felleisen.org/matthias/manifesto/ - MIT replaces Scheme with Python
https://www.johndcook.com/blog/2009/03/26/mit-replaces-scheme-with-python/ - Adventures in Advanced Symbolic Programming
http://groups.csail.mit.edu/mac/users/gjs/6.945/ - Why MIT Switched from Scheme to Python (2009)
https://news.ycombinator.com/item?id=14167453 - Starodávná stránka XLispu
http://www.xlisp.org/ - AutoLISP
https://en.wikipedia.org/wiki/AutoLISP - Seriál PicoLisp: minimalistický a výkonný interpret Lispu
https://www.root.cz/serialy/picolisp-minimalisticky-a-vykonny-interpret-lispu/ - Common Lisp
https://common-lisp.net/ - Getting Going with Common Lisp
https://cliki.net/Getting%20Started - Online Tutorial (Common Lisp)
https://cliki.net/online%20tutorial - Guile Emacs
https://www.emacswiki.org/emacs/GuileEmacs - Guile Emacs History
https://www.emacswiki.org/emacs/GuileEmacsHistory - Guile is a programming language
https://www.gnu.org/software/guile/ - MIT Scheme
http://groups.csail.mit.edu/mac/projects/scheme/ - SIOD: Scheme in One Defun
http://people.delphiforums.com/gjc//siod.html - CommonLispForEmacs
https://www.emacswiki.org/emacs/CommonLispForEmacs - Elisp: print, princ, prin1, format, message
http://ergoemacs.org/emacs/elisp_printing.html - Special Forms in Lisp
http://www.nhplace.com/kent/Papers/Special-Forms.html - Basic Building Blocks in LISP
https://www.tutorialspoint.com/lisp/lisp_basic_syntax.htm - Introduction to LISP – University of Pittsburgh
https://people.cs.pitt.edu/~milos/courses/cs2740/Lectures/LispTutorial.pdf - Why don't people use LISP
https://forums.freebsd.org/threads/why-dont-people-use-lisp.24572/ - Structured program theorem
https://en.wikipedia.org/wiki/Structured_program_theorem - Clojure: API Documentation
https://clojure.org/api/api