Obsah
1. Různé varianty funkce print
2. Malé zopakování z minula – smyčka implementovaná pomocí speciální formy while
3. Využití makra cl-loop pro konstrukci různých forem programových smyček
4. Použití klauzulí while a until
6. Specifikace počáteční hodnoty počitadla smyčky
7. Specifikace kroku, s nímž se mění hodnota počitadla smyčky
8. Snižování hodnoty počitadla smyčky
9. Procházení prvky seznamu aneb smyčka typu for-each
10. Rozdíl mezi formami for i in a for i on
11. Přímá změna hodnoty prvků seznamu, přes nějž se iteruje
13. Klauzule pro automatický výpočet sumy, nalezení maximálního a minimálního prvku atd.
14. Smyčka vracející programátorem specifikované (naakumulované) hodnoty
15. Lokální proměnné použité uvnitř těla smyčky
16. Alternativní funkcionální přístup při zpracování sekvencí – použití knihovny dash
18. Repositář s demonstračními příklady
1. Různé varianty funkce print
Dnešní článek je věnován především popisu univerzální programové smyčky cl-loop, ovšem ještě předtím se musíme alespoň ve stručnosti seznámit s různými variantami funkce print, protože tyto varianty budeme používat v demonstračních příkladech. V následující tabulce je vypsána většina funkcí, které lze použít pro zobrazení nějakých hodnot uživateli. Dnes využijeme zejména první tři funkce:
Funkce | Stručný popis |
---|---|
tisk hodnoty takovým způsobem, aby ji bylo možné načíst zpět pomocí read | |
prin1 | dtto, ovšem bez konce řádku |
princ | tisk hodnoty tak, aby byla dobře čitelná uživatelem (nekompatibilní s read) |
terpri | odřádkování (terminate print) |
message | zobrazení zprávy v bufferu (pro zprávy) |
insert | vložení zprávy do bufferu (jakoby byla zpráva napsána na klávesnici) |
Vyzkoušení vlastností prvních čtyř funkcí z předchozí tabulky:
(print 42) (print "Hello") (print :world) (print '(1 2 3)) (print "-----------------------------------------") (prin1 42) (terpri) (prin1 "Hello") (terpri) (prin1 :world) (terpri) (prin1 '(1 2 3)) (terpri) (print "-----------------------------------------") (princ 42) (terpri) (princ "Hello") (terpri) (princ :world) (terpri) (princ '(1 2 3)) (terpri)
Na standardním výstupu by se měl objevit následující text. Povšimněte si především různého způsobu zobrazení řetězců:
42 "Hello" :world (1 2 3) "-----------------------------------------" 42 "Hello" :world (1 2 3) "-----------------------------------------" 42 Hello :world (1 2 3)
2. Malé zopakování z minula – smyčka implementovaná pomocí speciální formy while
Vzhledem k tomu, že převážná část dnešního článku bude věnována různým formám programových smyček, zopakujeme si (velmi krátce) informace, které již známe z předchozích částí. Emacs Lisp, podobně jako jazyk Clojure a některé další dialekty Lispu, obsahuje ve své standardní knihovně jen jediný typ programové smyčky, a to konkrétně smyčku typu while. Chování této smyčky je známé z klasických strukturovaných jazyků, takže jen ve stručnosti:
(setq i 10) (while (> i 0) (princ (format "i = %d\n" i)) (setq i (- i 1)))
S výsledkem:
i = 10 i = 9 i = 8 i = 7 i = 6 i = 5 i = 4 i = 3 i = 2 i = 1
Počítání opačným směrem:
(setq i 0) (while (< i 10) (princ (format "i = %d\n" i)) (setq i (1+ i)))
S výsledkem:
i = 1 i = 2 i = 3 i = 4 i = 5 i = 6 i = 7 i = 8 i = 9
Speciální forma while na první pohled vypadá jednoduše použitelná. Současně se (z pohledu teorie) vlastně jedná o jediný typ programové smyčky, kterou je zapotřebí implementovat, protože všechny ostatní typy smyček je možné z while odvodit (viz též Structured program theorem). Ovšem z hlediska praktického použití je patrné, že se while používá poměrně složitě, protože je ji většinou zapotřebí doplnit o další pomocné konstrukce; v našem případě o počitadlo smyčky. Z tohoto důvodu byly do různých dialektů LISPu postupně přidávány i další typy smyček, přičemž nejvíce univerzální je smyčka loop realizovaná v Common Lispu. A právě možnostmi nabízenými touto smyčkou se budeme zabývat v navazujících kapitolách.
3. Využití makra cl-loop pro konstrukci různých forem programových smyček
(require 'cl-lib)
Smyčka loop ve formě, v jaké byla navržena v Common Lispu, programátorům nabízí svůj vlastní doménově specifický jazyk (DSL). Z dalších demonstračních příkladů bude patrné, že tento jazyk používá styl zápisu, který je kombinací klasických strukturovaných jazyků (Algol, Pascal, C) a možností LISPu. Je tomu tak z toho důvodu, aby bylo přímo ze zápisu smyčky, typicky již po přečtení prvního řádku, patrné, jak bude smyčka prováděna. K tomuto účelu se uvnitř smyčky loop používají symboly for, repeat, in, finally atd., které mají svůj speciální význam, ale pouze uvnitř samotné formy loop.
Podívejme se nyní na pravděpodobně nejjednodušší prakticky použitelný příklad využívající smyčku cl-loop, v níž bude použita dvojice symbolů se speciálním významem, o nichž jsme se zmínili v předchozím odstavci. Konkrétně budeme implementovat programovou smyčku, jejíž tělo se bude n-krát opakovat. K zápisu této varianty smyčky nám pomohou dva symboly se jmény repeat a do. Povšimněte si, že zápis smyčky vypadá prakticky stejně, jako by tomu bylo v některém z klasických strukturovaných jazyků (samozřejmě pokud si odmyslíme kulaté závorky, do kterých toto makro vkládáme a které jsou v Lispu povinné):
(cl-loop repeat počet-opakování do ...)
Podívejme se na praktický příklad (prozatím velmi jednoduchý):
(require 'cl-lib) (cl-loop repeat 10 do (princ "Hello world!\n"))
Makro cl-loop zavolané tímto způsobem nevrací žádnou hodnotu, takže se předpokládá, že smyčka vykoná svoji činnost jen díky tomu, že některá funkce volaná při každé iteraci bude mít vedlejší efekt, například že vypíše zprávu na obrazovku atd., což je ostatně přesně náš případ. Po spuštění výše popsané smyčky se na výstupu skutečně zobrazí deset totožných zpráv:
Hello world! Hello world! Hello world! Hello world! Hello world! Hello world! Hello world! Hello world! Hello world! Hello world!
4. Použití klauzulí while a until
Ve smyčce cl-loop je možné použít i klauzule while a until, za nimiž se zapisuje podmínka. Ta je od těla smyčky oddělena speciálním symbolem do, takže zápisy vypadají následovně:
Vyhodnocení podmínky na začátku každé iterace, tělo smyčky se zavolá, pokud je podmínka splněna:
(cl-loop while podmínka do ...)
Vyhodnocení podmínky na začátku každé iterace, tělo smyčky se zavolá, pokud podmínka splněna naopak není (po splnění podmínky se smyčka opustí):
(cl-loop until podmínka do ...)
Demonstrační příklad bude velmi jednoduchý, protože v něm použijeme jedinou řídicí proměnnou i, kterou budeme nejprve zmenšovat o jedničku a poté v druhé smyčce naopak zvyšovat až do chvíle, kdy překročí hodnotu 10:
(require 'cl-lib) (setq i 10) (cl-loop while (> i 0) do (princ (format "i = %d\n" i)) (setq i (- i 1))) (cl-loop until (> i 10) do (princ (format "i = %d\n" i)) (setq i (+ i 1)))
Výsledek:
i = 10 i = 9 i = 8 i = 7 i = 6 i = 5 i = 4 i = 3 i = 2 i = 1 i = 0 i = 1 i = 2 i = 3 i = 4 i = 5 i = 6 i = 7 i = 8 i = 9 i = 10
5. Počítané smyčky typu for
Makro cl-loop samozřejmě podporuje i tvorbu klasických programových smyček, v nichž se postupně mění hodnota počitadla. Základní forma této smyčky vypadá následovně:
(cl-loop for i to 10 do (princ (format "i = %d\n" i)))
Výsledek ukazuje, že se počítá od nuly a končí se až po dosažení koncové hodnoty (ne o jedničku dříve):
i = 0 i = 1 i = 2 i = 3 i = 4 i = 5 i = 6 i = 7 i = 8 i = 9 i = 10
Alternativně můžeme v tomto případě namísto symbolu to použít spíše upto:
(cl-loop for i upto 10 do (princ (format "i = %d\n" i)))
Se shodným výsledkem:
i = 0 i = 1 i = 2 i = 3 i = 4 i = 5 i = 6 i = 7 i = 8 i = 9 i = 10
Mnohdy potřebujeme, aby horní meze nebylo dosaženo a smyčka skončila těsně předtím. Namísto komplikovaných výpočtů použijte below a nikoli to či upto:
(cl-loop for i below 10 do (princ (format "i = %d\n" i)))
Skutečně se počítá jen do 9 a nikoli 10:
i = 0 i = 1 i = 2 i = 3 i = 4 i = 5 i = 6 i = 7 i = 8 i = 9
Užitečná je možnost přidání další (libovolné) podmínky, která pro počitadlo musí platit. Jsou přeskočeny ty iterace, kdy podmínka není splněna (ovšem smyčka není ukončena):
(cl-loop for i below 10 when (cl-evenp i) do (princ (format "i = %d\n" i)))
Výsledkem je sekvence sudých čísel:
i = 0 i = 2 i = 4 i = 6 i = 8
Popř.:
(cl-loop for i below 10 when (cl-oddp i) do (princ (format "i = %d\n" i)))
Výsledkem je sekvence čísel lichých:
i = 1 i = 3 i = 5 i = 7 i = 9
6. Specifikace počáteční hodnoty počitadla smyčky
Samozřejmě je možné v případě potřeby specifikovat i počáteční hodnotu počitadla; tj. nemusí se vždy začínat na nule:
(cl-loop for i from 1 to 10 do (princ (format "i = %d\n" i)))
Výsledek:
i = 1 i = 2 i = 3 i = 4 i = 5 i = 6 i = 7 i = 8 i = 9 i = 10
Totožná smyčka, ovšem s jiným symbolem (lépe čitelným):
(cl-loop for i from 1 upto 10 do (princ (format "i = %d\n" i)))
Výsledek je stejný:
i = 1 i = 2 i = 3 i = 4 i = 5 i = 6 i = 7 i = 8 i = 9 i = 10
Při počítání směrem nahoru lze namísto from použít symbol upfrom se shodným významem:
(cl-loop for i upfrom 1 upto 10 do (princ (format "i = %d\n" i)))
Výsledek:
i = 1 i = 2 i = 3 i = 4 i = 5 i = 6 i = 7 i = 8 i = 9 i = 10
Kombinace from a below:
(cl-loop for i from 1 below 10 do (princ (format "i = %d\n" i)))
Výsledek je kratší o poslední iteraci:
i = 1 i = 2 i = 3 i = 4 i = 5 i = 6 i = 7 i = 8 i = 9
Další kombinace již obsahují klauzuli when:
(cl-loop for i from 1 to 10 when (cl-evenp i) do (princ (format "i = %d\n" i)))
Výsledek:
i = 2 i = 4 i = 6 i = 8 i = 10
(cl-loop for i from 1 upto 10 when (cl-evenp i) do (princ (format "i = %d\n" i)))
Výsledek:
i = 2 i = 4 i = 6 i = 8 i = 10
(cl-loop for i upfrom 1 upto 10 when (cl-evenp i) do (princ (format "i = %d\n" i)))
Výsledek:
i = 2 i = 4 i = 6 i = 8 i = 10
(cl-loop for i from 1 below 10 when (cl-evenp i) do (princ (format "i = %d\n" i)))
Výsledek:
i = 2 i = 4 i = 6 i = 8
7. Specifikace kroku, s nímž se mění hodnota počitadla smyčky
Další příklad si již ukážeme v celku. Používá se v něm symbol by, za nímž se udává krok, tj. o jakou hodnotu se bude počitadlo měnit. Výchozí hodnotou je pochopitelně jednička:
(require 'cl-lib) (cl-loop for i from 0 to 30 by 3 do (princ (format "i = %d\n" i))) (princ "---------------------------\n") (cl-loop for i from 0 upto 30 by 3 do (princ (format "i = %d\n" i))) (princ "---------------------------\n") (cl-loop for i upfrom 0 upto 30 by 3 do (princ (format "i = %d\n" i))) (princ "---------------------------\n") (cl-loop for i upfrom 0 below 30 by 3 do (princ (format "i = %d\n" i))) (princ "===========================\n") (cl-loop for i from 0 to 30 by 3 when (cl-evenp i) do (princ (format "i = %d\n" i))) (princ "---------------------------\n") (cl-loop for i from 0 upto 30 by 3 when (cl-evenp i) do (princ (format "i = %d\n" i))) (princ "---------------------------\n") (cl-loop for i upfrom 0 upto 30 by 3 when (cl-evenp i) do (princ (format "i = %d\n" i))) (princ "---------------------------\n") (cl-loop for i upfrom 0 below 30 by 3 when (cl-evenp i) do (princ (format "i = %d\n" i)))
Podívejme se nyní na výsledky produkované tímto příkladem:
i = 0 i = 3 i = 6 i = 9 i = 12 i = 15 i = 18 i = 21 i = 24 i = 27 i = 30 --------------------------- i = 0 i = 3 i = 6 i = 9 i = 12 i = 15 i = 18 i = 21 i = 24 i = 27 i = 30 --------------------------- i = 0 i = 3 i = 6 i = 9 i = 12 i = 15 i = 18 i = 21 i = 24 i = 27 i = 30 --------------------------- i = 0 i = 3 i = 6 i = 9 i = 12 i = 15 i = 18 i = 21 i = 24 i = 27 =========================== i = 0 i = 6 i = 12 i = 18 i = 24 i = 30 --------------------------- i = 0 i = 6 i = 12 i = 18 i = 24 i = 30 --------------------------- i = 0 i = 6 i = 12 i = 18 i = 24 i = 30 --------------------------- i = 0 i = 6 i = 12 i = 18 i = 24
8. Snižování hodnoty počitadla smyčky
Prozatím jsme v předchozích smyčkách počitadlo vždy zvyšovali, ať již o jedničku nebo o jinou hodnotu. Počítat je však možné i opačným směrem. V tomto případě však nestačí za by zadat záporné číslo! Je nutné použít jiný zápis, a to s využitím symbolů downto nebo above. Opět si ukažme některé povolené kombinace (pozor na rozdílné chování downto a above):
(require 'cl-lib) (cl-loop for i from 10 downto 1 do (princ (format "i = %d\n" i))) (princ "---------------------------\n") (cl-loop for i from 10 above 1 do (princ (format "i = %d\n" i))) (princ "---------------------------\n") (cl-loop for i upfrom 10 above 1 do (princ (format "i = %d\n" i))) (princ "---------------------------\n") (cl-loop for i upfrom 10 above 1 by 2 do (princ (format "i = %d\n" i))) (princ "===========================\n") (cl-loop for i from 10 downto 1 when (cl-evenp i) do (princ (format "i = %d\n" i))) (princ "---------------------------\n") (cl-loop for i from 10 above 1 when (cl-evenp i) do (princ (format "i = %d\n" i))) (princ "---------------------------\n") (cl-loop for i upfrom 10 above 1 when (cl-evenp i) do (princ (format "i = %d\n" i))) (princ "---------------------------\n") (cl-loop for i upfrom 10 above 1 by 2 when (cl-evenp i) do (princ (format "i = %d\n" i))) (princ "---------------------------\n") (cl-loop for i upfrom 10 above 1 by 2 when (cl-oddp i) do (princ (format "i = %d\n" i))) (princ "---------------------------\n")
Po spuštění budou výsledky následující:
i = 10 i = 9 i = 8 i = 7 i = 6 i = 5 i = 4 i = 3 i = 2 i = 1 --------------------------- i = 10 i = 9 i = 8 i = 7 i = 6 i = 5 i = 4 i = 3 i = 2 --------------------------- i = 10 i = 9 i = 8 i = 7 i = 6 i = 5 i = 4 i = 3 i = 2 --------------------------- i = 10 i = 8 i = 6 i = 4 i = 2 =========================== i = 10 i = 8 i = 6 i = 4 i = 2 --------------------------- i = 10 i = 8 i = 6 i = 4 i = 2 --------------------------- i = 10 i = 8 i = 6 i = 4 i = 2 --------------------------- i = 10 i = 8 i = 6 i = 4 i = 2 --------------------------- ---------------------------
9. Procházení prvky seznamu aneb smyčka typu for-each
V případě, že se má procházet všemi prvky seznamu (nebo vektoru), lze použít klauzuli for in, která používá následující styl zápisu:
(cl-loop for prvek in seznam ... ... ... tělo smyčky ... ... ...)
Vidíme, že se tento zápis opět do značné míry podobá syntaxi, s níž se setkáme v běžných programovacích jazycích. Typicky se tato smyčka používá ve chvíli, kdy se v ní volá funkce s vedlejším efektem. Pokud tomu tak není a je nutné ze smyčky vrátit výsledek aplikace nějaké funkce na prvky seznamu, používá se klauzule collect, a to přibližně tímto způsobem:
(cl-loop for prvek in seznam collect prvek)
V následujícím příkladu je tato varianta smyčky použita, i když se ve skutečnosti dá nahradit funkcí mapcar:
(require 'cl-lib) (setq lst (number-sequence 0 10)) (print (cl-loop for i in lst collect i)) (print (cl-loop for i in lst collect (* i i))) (defun factorial (n) (let ((accumulator 1)) (dolist (value (number-sequence 1 n)) (setq accumulator (* accumulator value))) accumulator)) (print (cl-loop for i in lst collect (factorial i)))
Výsledky:
(0 1 2 3 4 5 6 7 8 9 10) (0 1 4 9 16 25 36 49 64 81 100) (1 1 2 6 24 120 720 5040 40320 362880 3628800)
10. Rozdíl mezi formami for i in a for i on
Existuje ještě jedna varianta smyčky for-each, ovšem tato varianta používá zápis se symbolem on a nikoli in:
(cl-loop for prvek on seznam collect prvek)
Tato varianta prochází seznamem odlišně – v první iteraci se do řídicí proměnné smyčky vloží celý seznam, ve druhé iteraci seznam bez prvního prvku atd. atd.:
(require 'cl-lib) (setq lst (number-sequence 0 10)) (setq result (cl-loop for i on lst collect i)) (dolist (item result) (print item))
Výsledek nyní bude značně odlišný – bude se jednat o seznam seznamů:
(0 1 2 3 4 5 6 7 8 9 10) (1 2 3 4 5 6 7 8 9 10) (2 3 4 5 6 7 8 9 10) (3 4 5 6 7 8 9 10) (4 5 6 7 8 9 10) (5 6 7 8 9 10) (6 7 8 9 10) (7 8 9 10) (8 9 10) (9 10) (10)
11. Přímá změna hodnoty prvků seznamu, přes nějž se iteruje
Nyní se dostáváme do oblasti, v níž se vlastnosti makra cl-loop již prakticky ztotožnily s možnostmi nefunkcionálních jazyků, se všemi výhodami a nevýhodami, které tento přístup přináší. V některých situacích je totiž vhodné, aby programová smyčka přímo měnila hodnoty seznamu (nebo pole), jehož prvky se ve smyčce prochází. I to je možné, a to díky následující konstrukci:
(cl-loop for prvek in-ref seznam do ... ... ... tělo smyčky ... ... ...)
Povšimněte si rozdílu oproti předchozím smyčkám – namísto speciálního symbolu in je použit symbol in-ref. Díky použití tohoto symbolu se v každé iteraci získá reference na prvek procházeného seznamu, nikoli hodnota. Této vlastnosti můžeme využít například pro zvýšení hodnoty prvku v seznamu o jedničku pomocí funkce cl-incf:
(cl-loop for i in-ref lst do (cl-incf i)))
Po zavolání této smyčky bude původní seznam lst obsahovat nové hodnoty (o jedničku zvýšené). Podívejme se nyní na demonstrační příklad, v němž bude použit právě tento typ smyčky, to hned dvakrát. V první smyčce zvýšíme hodnotu prvků původního seznamu o jedničku, ve smyčce druhé pak prvky vynulujeme:
(require 'cl-lib) (setq lst (number-sequence 0 10)) (print lst) (print (cl-loop for i in-ref lst do (cl-incf i))) (print lst) (cl-loop for i in-ref lst do (setf i 0)) (print lst)
Po spuštění se na výstupu objeví následující řádky:
(0 1 2 3 4 5 6 7 8 9 10) nil (1 2 3 4 5 6 7 8 9 10 11) (0 0 0 0 0 0 0 0 0 0 0)
12. Klauzule append
V předchozích příkladech jsme pro získání výsledné hodnoty smyčky používali klauzuli collect. Použít je možné i klauzuli append, která pracuje podobně, ale pokud této klauzuli předáme seznam, budou všechny jeho prvky přidány do výsledného seznamu (každý zvlášť). Můžeme tím tedy nahradit operaci flatten:
(require 'cl-lib) (setq letters '((alfa beta) () (gama delta) (omega) ())) (setq result (cl-loop for i in letters append i)) (dolist (item result) (print item))
alfa beta gama delta omega
13. Klauzule pro automatický výpočet sumy, nalezení maximálního a minimálního prvku atd.
Podívejme se ještě na některé složitější konstrukce, které dokážeme s makrem cl-loop vytvořit. Poměrně často se setkáme se situací, kdy je nutné vypočítat sumu všech prvků nějakého seznamu nebo vektoru. To lze provést několika způsoby (reduce atd.), ovšem při použití smyčky cl-loop lze využít klauzuli sum pro akumulaci výsledků:
(require 'cl-lib) (setq lst (number-sequence 0 10)) (setq result (cl-loop for i in lst sum i)) (princ (format "Result: %d" result))
Předchozí zápis vracel implicitně jedinou hodnotu ze smyčky, a to sumu prvků. Toto chování lze popsat i explicitně s využitím klauzule finally, do níž zapíšeme příkaz, který se má vykonat při ukončování smyčky. Povšimněte si, že zde používáme lokální proměnnou total (lze ji pojmenovat různě):
(setq result (cl-loop for i in lst sum i into total finally return total)) (princ (format "Result: %d" result))
Výsledkem bude v obou případech:
Result: 55
14. Smyčka vracející programátorem specifikované (naakumulované) hodnoty
Předchozí příklad je možné ještě více „vyšperkovat“, například vypočítat počet všech prvků, jejich součet, maximální hodnotu a minimální hodnotu. To vše v jediné smyčce a bez použití podmínek. Povšimněte si způsobu, jak ze smyčky vrátit více hodnot:
(require 'cl-lib) (setq lst (number-sequence 0 10)) (setq result (cl-loop for i in lst count i into counter sum i into total maximize i into max-value minimize i into min-value finally return (list min-value max-value total counter))) (princ (format "Min value %d\n" (nth 0 result))) (princ (format "Max value %d\n" (nth 1 result))) (princ (format "Sum value %d\n" (nth 2 result))) (princ (format "Values %d\n" (nth 3 result)))
Výsledky vypočtené předchozím příkladem:
Min value 0 Max value 10 Sum value 55 Values 11
Zcela stejně můžeme postupovat při analýze generátoru náhodných čísel. Povšimněte si, že v klauzuli finally pochopitelně můžeme volat nějakou funkci, zde dělení (výpočet průměru):
(require 'cl-lib) (setq rnd (cl-loop repeat 1000 collect (random 10000))) (setq result (cl-loop for x in rnd count x into counter sum x into total maximize x into max-value minimize x into min-value finally return (list min-value max-value total (/ total counter) counter))) (princ (format "Min value %d\n" (nth 0 result))) (princ (format "Max value %d\n" (nth 1 result))) (princ (format "Sum value %d\n" (nth 2 result))) (princ (format "Avg value %d\n" (nth 3 result))) (princ (format "Values %d\n" (nth 4 result)))
Výsledky (mohou se pochopitelně lišit):
Min value 0 Max value 9990 Sum value 4992707 Avg value 4992 Values 1000
15. Lokální proměnné použité uvnitř těla smyčky
V posledním příkladu s makrem cl-loop je ukázáno, že uvnitř programové smyčky je možné v případě potřeby použít lokální proměnné. Tato smyčka slouží k vyhledání odmocniny nějaké vstupní hodnoty, popř. celého čísla, které je této odmocnině nejblíže. Povšimněte si způsobu zápisu koncové podmínky pomocí until (ta se ovšem kombinuje s from to, takže se nikdy nebude jednat o nekonečnou smyčku):
(require 'cl-lib) (defun find-sqrt (value) (cl-loop for x from 1 to 200 for square = (* x x) until (>= square value) finally return x)) (princ (format "Sqrt of %d = %d\n" 1764 (find-sqrt 1764)))
Výsledek předchozího příkladu:
Sqrt of 1764 = 42
16. Alternativní funkcionální přístup při zpracování sekvencí – použití knihovny dash
V samotném závěru dnešního článku se zmíníme o zajímavé knihovně nazvané dash, jejíž zdrojové kódy je možné nalézt na GitHubu, konkrétně na adrese https://github.com/magnars/dash.el. Makra a funkce, které jsou touto knihovnou nabízeny, jsou inspirovány standardní knihovnou programovacího jazyka Clojure, zejména funkcemi pro funkcionálně pojaté zpracování různých typů sekvencí (filtrace, redukce, aplikace funkce na prvky sekvence, rozdělení sekvence atd).
To ovšem není vše, protože knihovna dash obsahuje i takzvaná threading makra, která mohou být velmi užitečná a navíc mohou zvýšit čitelnost zdrojových kódů. Při použití threading maker je možné zredukovat počet kulatých závorek, ovšem mnohem důležitější je fakt, že sekvence zápisu funkcí v programovém kódu bude odpovídat jejich volání (ve standardním Lispu je to vlastně naopak). Dnes se seznámíme jen s nejzákladnějšími funkcemi a makry poskytovanými touto knihovnou, její další možnosti budou popsány v samostatném článku.
Nejdříve si na několika screenshotech ukážeme, jakým způsobem se knihovna dash instaluje. Nebude to nic těžkého, pouze stačí mít povolen repositář Melpa, což jsme si již ukázali v článku o pluginu Evil:
Obrázek 1: Zadáme příkaz M-x list-packages.
Obrázek 2: Nalezneme balíček s knihovnou dash.
Obrázek 3: Provedeme instalaci (tlačítko Install).
Do .emacs doplníme:
(require 'dash)
Podívejme se nyní na některé základní možnosti, které programátorům knihovna dash nabízí. Základní funkcí (vyššího řádu), kterou v této knihovně naleznete, je klasická funkce map, která se zde ovšem jmenuje -map nebo –map. Příklad použití:
(package-initialize) (require 'dash) (defun factorial (n) (apply '* (number-sequence 1 n))) (print (-map 'factorial '(0 1 2 3 4 5 6 7 8 9 10)))
S výsledky:
(1 1 2 6 24 120 720 5040 40320 362880 3628800)
mapcar is a built-in function in ‘C source code’. (mapcar FUNCTION SEQUENCE) Apply FUNCTION to each element of SEQUENCE, and make a list of the results. The result is a list just as long as SEQUENCE. SEQUENCE may be a list, a vector, a bool-vector, or a string.
Předchozí příklad tedy můžeme přepsat na:
(print (mapcar 'factorial '(0 1 2 3 4 5 6 7 8 9 10)))
Samozřejmě je možné, aby se přímo použila anonymní funkce namísto pojmenované funkce factorial:
(print (-map (lambda (n) (apply '* (number-sequence 1 n))) '(0 1 2 3 4 5 6 7 8 9 10)))
A právě při čtení předchozího výrazu vás možná napadlo, že použití lambda je pro tak jednoduchou věc, jako je definice anonymní funkce používané na jediném místě, zbytečně zdlouhavé. Pro kratší zápis je možné použít variantu –map:
(print (--map (apply '* (number-sequence 1 it)) '(0 1 2 3 4 5 6 7 8 9 10)))
Povšimněte si toho, že namísto anonymní funkce můžeme napsat přímo její tělo a v těle lze použít symbol it, který nahrazuje pojmenovaný parametr n z předchozího příkladu. it je v tomto případě rezervovaný symbol s přesně tímto význame (podobný % v Clojure), protože ve skutečnosti je –map makrem, s následující definicí:
(defmacro --map (form list) "Anaphoric form of `-map'." (declare (debug (form form))) `(mapcar (lambda (it) ,form) ,list))
Expanzí volání tohoto makra získáme kód (přesněji řečeno formu), který se podobá prvnímu příkladu s plnohodnotnou anonymní funkci (pro tyto účely můžeme ignorovat řádek s výstupem pro ladění).
(mapcar (lambda (it) (apply '* (number-sequence 1 it))) '(0 1 2 3 4 5 6 7 8 9 10))
Ještě se v rychlosti podíváme na další užitečnou funkci pojmenovanou -filter, jejíž význam a různé varianty budou popsány příště:
(package-initialize) (require 'dash) (require 'cl-lib) (setq numbers (number-sequence 0 30)) (print numbers) (print (-filter 'cl-evenp numbers)) (print (-filter 'cl-oddp numbers)) (print (-filter (lambda (n) (zerop (% n 3))) numbers)) (print (--filter (zerop (% it 3)) numbers))
Výsledky filtrace původních seznamu se 31 hodnotami 0..30:
(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30) (0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30) (1 3 5 7 9 11 13 15 17 19 21 23 25 27 29) (0 3 6 9 12 15 18 21 24 27 30) (0 3 6 9 12 15 18 21 24 27 30)
17. Threading makra
V programovacím jazyku Clojure existuje dvojice velmi užitečných maker, která je možné použít pro zjednodušení zápisu „kolony“ funkcí, kterým se postupně předávají výsledky předchozích funkcí. Jedná se vlastně o lispovskou obdobu klasické Unixové kolony používané při spojování filtrů v shellu (ls –1 | sort atd.). V Clojure je možné namísto poměrně nečitelného zápisu:
(f (g (h x)))
použít zápis:
(-> x h g f)
Tímto zápisem se specifikuje, že se nějaká hodnota x předá funkci h, výsledek této funkce se předá funkci g a výsledek funkce g se nakonec předá funkci f. Podobně fungující makro je dostupné i v knihovně dash a tím pádem pochopitelně i v celém Emacs Lispu (pokud si pochopitelně tuto knihovnu naimportujete), takže si ho můžeme ihned vyzkoušet:
(-> (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. Tento parametr není ve zdrojovém textu viditelný:
(-> (number-sequence 0 30) length (+ 1) print)
S výsledkem:
32
V mnoha případech nám však nemusí vyhovovat, že se předchozí výsledek předá další funkci v koloně v prvním parametru. Dobrým příkladem mohou být již výše zmíněné funkce vyššího řádu. Pokud se tyto funkce používají v koloně, musí se namísto makra → použít druhé threading makro nazvané ->>, které předchozí výsledek předá další funkci v posledním parametru, nikoli v parametru prvním. Opět se podívejme na jednoduchý příklad:
(->> (number-sequence 0 30) (-filter 'cl-evenp) (-filter (lambda (n) (zerop (% n 3)))) reverse print)
Výsledek by měl vypadat takto:
(30 24 18 12 6 0)
V předchozím jsme funkci vyššího řádu -filter předávali složitější predikáty, které si samozřejmě můžeme pro větší čitelnost definovat v nových uživatelských funkcích:
(defun even-numbers (numbers) (-filter 'cl-evenp numbers)) (defun div-by-three-numbers (numbers) (-filter (lambda (n) (zerop (% n 3))) numbers))
Následně se nám předchozí příklad zjednoduší:
(->> (number-sequence 0 30) even-numbers div-by-three-numbers reverse print)
Výsledek bude shodný s předchozím příkladem:
(30 24 18 12 6 0)
První hodnotou, která do threading makra vstupuje, může být přímo zpracovávané číslo (bude ihned předáno generátoru number-sequence ve druhém parametru):
(->> 100 (number-sequence 0) even-numbers div-by-three-numbers reverse print)
Výsledek:
(96 90 84 78 72 66 60 54 48 42 36 30 24 18 12 6 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:
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