Obsah
3. Práce s globálními proměnnými a konstantami
4. Dokumentační řetězce u proměnných a konstant
5. Koncept lokálních proměnných
6. „Paralelní“ versus „sekvenční“ inicializace lokálních proměnných
7. Pravdivostní hodnoty a základní booleovské operace
8. Další booleovské operace a predikáty
9. Řízení běhu programu – rozvětvení
10. Použití forem when a unless
11. Vícenásobné rozvětvení – forma cond
12. Programové smyčky ve funkcionálních jazycích?
13. Základní programové smyčky založené na speciální formě while
17. Otestování jednoduché interaktivní funkce
18. Repositář s demonstračními příklady
1. Vyhodnocování seznamů
„Learning Lisp is like climbing a hill in which the first part is the steepest.“
V programovacím jazyku Emacs Lisp existují čtyři základní typy forem:
Forma | Příklad | Vyhodnocení |
---|---|---|
literály | celé číslo, float, řetězec, … | vyhodnocují se samy na sebe |
speciální symboly | nil, t | vyhodnocují se samy na sebe, nelze je měnit |
složené formy | seznamy, vektory, asociativní pole | rekurzivní u seznamů (viz další text) |
speciální formy | let, quote, setq, if, lambda | různý způsob vyhodnocení (typicky odlišný od seznamů) |
Seznamy mají v programovacím jazyku Emacs Lisp kromě klasické funkce (datový typ) ještě jeden dosti zásadní význam. Samotný program zapsaný v Emacs Lispu není totiž nic jiného než seznam (či seznamy), přičemž prvním prvkem seznamu je jméno funkce a zbylé prvky seznamu jsou chápány jako parametry této funkce. Příkladem může být například funkce nazvaná print, která vypíše obsah svých parametrů na standardní výstup a posléze je provedeno odřádkování. Klasický program typu „Hello world“ by tedy mohl v Emacs Lispu vypadat následovně:
(println "Hello" "world") Hello world
Funkce samozřejmě mohou vracet i nějaké výsledky, což nám umožňuje zapisovat program funkcionálním stylem – tedy jako funkci, jejíž parametry jsou vyhodnoceny na základě nějakých jiných funkcí vyhodnocovaných rekurzivně. Mějme například funkci pojmenovanou + (což je v Lispu zcela korektní jméno funkce), která provede součet všech svých parametrů, a dále funkci pojmenovanou *, která naopak vynásobí všechny své parametry. Můžeme tedy psát:
; + akceptuje libovolný počet argumentů (+ 1 2 3) 6
nebo též:
(* 6 7) 42
Ovšem taktéž je možné použít následující zápis, kdy se nejdříve vyhodnotí vnitřní funkce a teprve poté se jejich výsledky použijí pro násobení:
(* (+ 1 2 3) (+ 3 4)) 42
Vyhodnocení probíhá v těchto krocích:
(* (+ 1 2 3) (+ 3 4)) (* 6 (+ 3 4)) (* 6 7) 42
Pravidla pro vyhodnocení forem jsou v programovacím jazyku Lisp velmi jednoduchá a přímočará, na rozdíl od mnoha jiných programovacích jazyků. Tato pravidla lze ve zjednodušené podobě sepsat do několika bodů:
- čísla, řetězce, pravdivostní hodnoty a další literály jsou vyhodnoceny samy na sebe (což je logické – jedná se o dále nedělitelné objekty)
- hodnotou symbolu je objekt, který je na tento symbol navázán (analogie z jiných programovacích jazyků – hodnotou proměnné zapsané svým jménem je hodnota uložená do proměnné)
- seznamy jsou vyhodnocovány tak, že se první prvek seznamu chápe jako jméno funkce (či speciální formy), které je předán zbytek seznamu jako parametry této funkce (formy)
- pokud seznam obsahuje podseznamy, jsou tyto podseznamy vyhodnoceny nejdříve, přičemž úroveň rekurzivního zanořování při vyhodnocování podseznamů není teoreticky omezena (tj. podseznamy lze vnořovat do téměř libovolné úrovně). To jsme ostatně viděli už na předchozím příkladu.
2. Speciální formy
Další velmi důležitou vlastností programovacího jazyka Emacs Lisp, s níž se v dnešním článku alespoň ve stručnosti seznámíme, je použití takzvaných speciálních forem. Ze syntaktického hlediska jsou speciální formy zapisovány naprosto stejným způsobem jako běžné funkce (tj. v kulatých závorkách je nejprve zapsáno jméno funkce resp. formy a posléze její parametry), ovšem existuje zde jeden významný rozdíl – zatímco u funkcí jsou všechny jejich parametry nejdříve rekurzivně vyhodnoceny a teprve posléze je funkce zavolána, u speciálních forem k tomuto vyhodnocení obecně nedochází, resp. jsou vyhodnoceny pouze některé parametry (které konkrétně, to závisí na tom, o jakou speciální formu se jedná).
K čemu jsou speciální formy dobré? Typickým a pro praxi naprosto nezbytným příkladem je zápis podmíněných bloků kódu. V tomto případě potřebujeme, aby se nějaká část programu vykonala pouze v případě, že je splněna (popř. nesplněna) nějaká podmínka, v opačném případě nemá být tato část programu vůbec vykonána.
Představme si například tuto situaci (formu if si podrobněji popíšeme v deváté kapitole):
(if (message-box "Smazat disk?") (delete-filesystem))
Což je jen pěkně upravené volání formy if se dvěma parametry:
(if (message-box "Smazat disk?") (delete-filesystem))
Pomocí běžných funkcí by nebylo možné tuto funkcionalitu splnit, protože by se kód (předaný jako parametr – jinou možnost v Lispu ostatně prakticky nemáme) vykonal ještě před zavoláním „podmínkové“ funkce. Z toho vyplývá, že samotná podmínka, i když se syntakticky podobá volání funkce, je speciální formou. V jazyku Emacs Lisp existuje pro zápis podmíněného příkazu mj. i speciální forma if, která očekává tři parametry:
- podmínku (výraz=formu, která se vyhodnotí na t či nil
- formu vyhodnocenou v případě, že je podmínka splněna
- formu vyhodnocenou v případě, že podmínka není splněna
V Emacs Lispu najdeme tyto speciální formy:
and |
catch |
cond |
condition-case |
defconst |
defvar |
function |
if |
interactive |
lambda |
let |
let* |
or |
prog1 |
prog2 |
progn |
quote |
save-current-buffer |
save-excursion |
save-restriction |
setq |
setq-default |
track-mouse |
unwind-protect |
while |
3. Práce s globálními proměnnými a konstantami
Prozatím jsme se v demonstračních příkladech povětšinou zabývali pouze funkcemi, které nějakým způsobem zpracovávaly své parametry a popř. vracely nějaký vypočtený výsledek. Ovšem Emacs Lisp (na rozdíl od některých jiných dialektů LISPu) není pouze akademický jazyk, takže umožňuje i práci s proměnnými, samozřejmě včetně proměnných globálních (jejichž výskyt by se sice měl v programech co nejvíce omezit, ovšem v Emacsu jsou i některé interní struktury editoru reprezentovány globálními proměnnými). Globální i lokální proměnné jsou měnitelné, protože Emacs Lisp není, ostatně stejně jako většina ostatních LISPových dialektů, čistě funkcionální. Pro základní práci s proměnnými je určeno těchto několik funkcí a speciálních forem:
# | Funkce | Význam |
---|---|---|
1 | set | nastavení proměnné na určitou hodnotu, proměnná musí být quotována |
2 | setq | nastavení proměnné na určitou hodnotu, proměnná je quotována automaticky |
3 | defconst | definice konstanty popř. i jejího dokumentačního řetězce |
4 | defvar | definice proměnné popř. i jejího dokumentačního řetězce |
Podívejme se nyní na způsob použití těchto funkcí a speciálních forem. Pro nastavení hodnoty proměnné či proměnných se používá forma set. Jména proměnných musí být v tomto případě quotována (apostrof před jménem):
(set 'zero 0) (print zero) 0
Mnohem praktičtější je namísto funkce set použít formu nazvanou setq, která quotování nepotřebuje. Proto se s touto formou setkáme v programech mnohem častěji:
(setq answer 42) (print answer) 42
Proměnnou definovanou přes set nebo setq lze změnit (je tedy mutable):
(setq answer 43) (print answer) 43
Pomocí speciální formy setq je možné vytvořit více proměnných jediným příkazem:
(setq x 10 y 20) (print x) 10 (print y) 20
Alternativně je možné proměnné definovat s využitím další speciální formy pojmenované defvar. Na rozdíl od setq je možné pomocí defvar definovat jen jednu proměnnou. Pokud již proměnná existuje, nebude přepsána!
(defvar e 2.7172) (print e) 2.7172 (defvar e 5.55) (print e) 2.7172
Konstanty se definují pomocí speciální formy defconst:
(defconst pi 3.141592653589793) (print pi) 3.141592653589793
A lze je později změnit, ovšem jen pomocí defconst:
(defconst pi 3) (print pi) 3
4. Dokumentační řetězce u proměnných a konstant
Další význam speciálních forem defconst a defvar spočívá v tom, že je možné ke konstantě či proměnné přidat takzvaný dokumentační řetězec, který se automaticky stane součástí nápovědy Emacsu. Použití je snadné:
(defconst pi-float 3.141592653589793 "Ratio of a circle's circumference to its diameter") (print pi-float) (print (describe-variable 'pi-float))
Mělo by se vypsat:
3.141592653589793 "pi-float’s value is 3.141592653589793 This variable may be risky if used as a file-local variable. Documentation: Ratio of a circle’s circumference to its diameter"
Podobně si můžeme vypsat dokumentaci k symbolu načteného z jiného modulu:
(print (describe-variable 'float-pi))
S výsledkem:
#("float-pi is a variable defined in ‘float-sup.el’. Its value is 3.141592653589793 This variable may be risky if used as a file-local variable. Documentation: The value of Pi (3.1415926...)."
Příklad přidání dokumentačního řetězce k proměnné:
(defvar delta 4.669201609102990671853203821 "Feigenbaum constant") (print delta) (print (describe-variable 'delta))
S výsledkem:
4.66920160910299 "delta’s value is 4.66920160910299 Documentation: Feigenbaum constant"
Proměnná bez dokumentačního řetězce:
(defvar unknown nil) (print unknown) (print (describe-variable 'unknown))
S výsledkem:
nil "unknown’s value is nil Documentation: Not documented as a variable."
5. Koncept lokálních proměnných
V mnoha funkcích se používají lokální proměnné. Ty se ovšem (na rozdíl od proměnných globálních) nedefinují pomocí formy set ani setq, ale další speciální formou pojmenovanou let, která současně omezuje platnost takto vytvořených proměnných. Do formy let se totiž zapisuje jak deklarace a inicializace proměnné, tak i vlastní tělo, tj. seznam výrazů. Návratovou hodnotou celého bloku je hodnota vrácená posledním výrazem. Nejjednodušší příklad s deklarací jediné lokální proměnné může vypadat následovně:
(let ((x 10)) (* x 2)) 20
Při deklaraci dvou proměnných (či ještě většího počtu proměnných) se používají závorky:
(let ((x 6) (y 7)) (* x y)) 42
Lze přepsat i takto:
(let ((x 6) (y 7)) (* x y)) 42
Následuje příklad použití speciální formy let uvnitř funkce. Zde je návratová hodnota let současně i návratovou hodnotou celé funkce:
(defun prumer (seznam) (let ((soucet (apply '+ seznam)) (pocet (length seznam))) (/ soucet pocet)))
Můžeme snadno provést otestování nové funkce:
(print (prumer '(1 2 3 4 5 6 7 8 9))) 5
Formy let je možné v případě potřeby libovolným způsobem rekurzivně zanořovat. Oblast viditelnosti proměnných je stále určena blokem let, ve kterém je proměnná deklarována. Zde má tedy proměnná nazvaná vysledek jen omezenou platnost viditelnosti:
(defun prumer-2 (seznam) (let ((soucet (apply '+ seznam))) (let ((pocet (length seznam))) (/ soucet pocet))))
Test proběhne stejně jako v předchozím příkladu:
(print (prumer-2 '(1 2 3 4 5 6 7 8 9))) 5
6. „Paralelní“ versus „sekvenční“ inicializace lokálních proměnných
Při deklaraci většího množství lokálních proměnných v jediné formě let si musíme dát pozor na to, že proměnné, které jsou deklarovány a inicializovány dříve, není možné použít i ve výrazech sloužících pro inicializaci později deklarovaných proměnných. Jinými slovy – přiřazení, které je deklarováno v let, se jakoby provádí paralelně.
Co to znamená v praxi? Podívejme se na následující funkci počítající délku přepony pravoúhlého trojúhelníku. Vytvoříme si tři pomocné proměnné a v průběhu jejich deklarace vlastně provedeme předběžné mezivýpočty (x2, y2, x2+y2):
(defun incorrect-hypot (x y) (let ((x2 (* x x)) (y2 (* y y)) (s (+ x2 y2))) (sqrt s)))
Pokusme se nyní tuto funkci zavolat:
(print (incorrect-hypot 3 4)) Symbol’s value as variable is void: x2
Vidíme, že zavolání skončilo s chybou, a to konkrétně u pokusu deklarace lokální proměnné s. Tato deklarace je sice uvedena až za řádky s proměnnými x2 a y2, ovšem to u formy let s „paralelním přiřazením“ nepomůže. Musíme namísto toho použít formu let*, která provádí přiřazení „sekvenčně“:
(defun correct-hypot (x y) (let* ((x2 (* x x)) (y2 (* y y)) (s (+ x2 y2))) (sqrt s)))
Nyní celý výpočet proběhne v pořádku:
(print (correct-hypot 3 4)) 5
(defn handler "Handler that is called by Ring for all requests received from user(s)." [request] (let [uri (:uri request) method (:request-method request) use-name (get params "user-name") use-id (get params "user-id")] ... ... ...))
7. Pravdivostní hodnoty a základní booleovské operace
V dialektu Emacs Lisp se pro reprezentaci pravdivostních hodnot true a false používají globální a současně neměnitelné speciální symboly nazvané t a nil (oba symboly jsou psány malými písmeny!). Argumenty dále popsaných booleovských operací mohou být libovolného typu (čísla, symboly atd.), s tím, že jakákoli hodnota rozdílná od nil (nebo prázdného seznamu, který je od nil nerozlišitelný) je považována za logickou pravdu. Při tvorbě programů jsou k dispozici všechny tři základní booleovské operace vypsané v následující tabulce:
# | Funkce | Význam |
---|---|---|
1 | and | logický součin |
2 | or | logický součet |
3 | not | logická negace |
První dvě výše vypsané operace jsou variadické, tj. lze jim předat libovolné množství parametrů. V případě funkce not má smysl použít jen jediný parametr:
(and t t) t (and t nil) nil (or t t) t (or t nil) t (or nil nil nil nil) nil (not nil) t (not t) nil
(setq x 10) (print (and (> x 0) "kladne")) "kladne" (setq y 0) (print (and (> y 0) "kladne")) nil
v porovnání:
(setq x 10) (print (if (> x 0) "kladne")) "kladne" (setq y 0) (print (if (> y 0) "kladne")) nil
8. Další booleovské operace a predikáty
K dialektům programovacího jazyka LISP samozřejmě patří i množina predikátů, tj. funkcí, které na základě nějaké vyhodnocené podmínky vrací pravdivostní hodnotu. Většina predikátů končí znakem p, ale nalezneme zde i několik výjimek (není však samozřejmě nijak problematické si dodefinovat i ostatní predikáty končící otazníkem nebo znakem p). V Emacs Lispu nalezneme poměrně velké množství predikátů:
Predikáty hodnot |
---|
null |
zerop |
Predikáty typů (test na typ) |
---|
atom |
arrayp |
bool-vector-p |
bufferp |
byte-code-function-p |
case-table-p |
char-or-string-p |
char-table-p |
commandp |
consp |
display-table-p |
floatp |
frame-configuration-p |
frame-live-p |
framep |
functionp |
integer-or-marker-p |
integerp |
keymapp |
listp |
markerp |
wholenump |
nlistp |
numberp |
number-or-marker-p |
overlayp |
processp |
sequencep |
stringp |
subrp |
symbolp |
syntax-table-p |
user-variable-p |
vectorp |
window-configuration-p |
window-live-p |
windowp |
Podívejme se nyní na několik jednoduchých příkladů použití predikátů:
(atom nil) t (atom t) t (atom 42) t (atom "string") t (atom '(1 2 3)) nil
(listp nil) t (listp t) nil (listp 42) nil (listp "string") nil (listp '(1 2 3)) t
Test, zda je hodnota nil (někdy lze použít pro logickou negaci):
(null nil) t (null t) nil (null 42) nil (null "string") nil (null '(1 2 3)) nil
Test, zda je hodnota neprázdným seznamem:
(consp '(1.2)) t (consp '( 1 2 3)) t (consp '()) nil (consp 42) nil
Test na nulovou hodnotu:
(zerop 0) t (zerop 42) nil
9. Řízení běhu programu – rozvětvení
Pro řízení běhu programu, tj. pro rozvětvení, nabízí Emacs Lisp poměrně velké množství různých speciálních forem. Základem je samozřejmě speciální forma nazvaná if s klasickým rozvětvením, ovšem skalní LISPaři v Emacs Lispu naleznou například i oblíbené cond či formy when a unless, jejichž použití může zpřehlednit zdrojový kód:
# | Forma | Stručný popis |
---|---|---|
1 | if | rozdělení výpočtu do dvou větví na základě podmínky |
2 | when | varianta if pro větší množství příkazů ve větvi „then“, viz též kapitolu 10 |
3 | unless | varianta if pro větší množství příkazů ve větvi „else“, viz též kapitolu 10 |
4 | cond | vícenásobné rozvětvení, LISPovská klasická a univerzální konstrukce, viz též jedenáctou kapitolu |
„Klasické“ rozvětvení je řešeno speciální formou if. Tu lze zapsat dvěma způsoby. Buď jako „plnou“ rozvětvovací konstrukci:
(if podmínka výraz1 výraz2)
Popř. je možné druhý výraz vynechat:
(if podmínka výraz1)
; na základě podmínky se vyhodnotí (a vrátí jako výsledek) ; buď řetězec "mensi" nebo "vetsi" (if (< 1 2) "mensi" "vetsi") "mensi"
; opačná podmínka - je vyhodnocen pouze druhý řetězec (if (> 1 2) "mensi" "vetsi") "vetsi"
; test na ekvivalenci (if (= 1 2) "rovno" "nerovno") "nerovno"
; další test na ekvivalenci (if (= 1 1) "rovno" "nerovno") "rovno"
Příklad použití speciální formy if ve funkci:
(defun test-nuly (x) (if (zerop x) "nula" "nenulova hodnota"))
Zkrácená podoba if:
(defun test-nuly-2 (x) (if (zerop x) "nula")) (print (test-nuly-2 0)) (print (test-nuly-2 1))
Formy if je možné v případě potřeby samozřejmě zanořovat. Příkladem může být rekurzivní funkce určená pro výpočet největšího společného dělitele dvou celočíselných hodnot:
(defun gcd (x y) (if (= x y) x (if (> x y) (gcd (- x y) y) (gcd x (- y x))))) (print (gcd 64 24))
10. Použití forem when a unless
V některé části aplikace většinou potřebujeme na základě nějaké podmínky vykonat sekvenci příkazů. I v tomto případě je samozřejmě možné použít if, ovšem celá sekvence příkazů musí být vytvořena v jiné funkci či „uzavřena“ do programového bloku tvořeného formou progn. Pokud například potřebujeme na základě vyhodnocené podmínky vypsat na standardní výstup zprávu a současně vrátit nějakou hodnotu, mohl by celý zápis vypadat například takto:
(setq a 20) (setq b 20) (if (zerop (- a b)) (progn (print "shodne hodnoty") (do-something) (do-something-else) (+ a b)))
Pro zlepšení čitelnosti zdrojového kódu je možné použít formu when, která sice nedokáže provést úplné rozvětvení tak, jako forma if (zapisuje se vždy jen jedna větev), ale zato se za ni může napsat libovolné množství forem (příkazů), které se postupně vyhodnotí při splnění zadané podmínky. Výše uvedený příklad se nám tedy zjednoduší:
(setq a 20) (setq b 20) (when (zerop (- a b)) (print "shodne hodnoty") (do-something) (do-something-else) (+ a b))
Opakem speciální formy when je forma nazvaná unless. Jediným rozdílem je zde negace podmínky, což znamená, že unless lze přepsat na when, ovšem samotnou podmínku je zapotřebí negovat, což je samozřejmě daleko méně čitelné (a navíc vyžaduje zápis dalších závorek), než přímé použití unless:
(setq a 20) (setq b 20) (unless (zerop (- a b)) (print "rozdilne hodnoty") (do-something) (do-something-else) (+ a b))
11. Vícenásobné rozvětvení – forma cond
Speciální forma cond je pravděpodobně skalním lispařům dobře známá, protože se vlastně jedná o zobecnění rozeskoku na libovolný počet podmínek a větví. Této formě se předává předem neomezený počet dvojic, kde první prvek dvojice představuje podmínku a druhý prvek pak tělo (výraz), který se zavolá při splnění této podmínky. Výsledek vybraného výrazu je současně i výsledkem vyhodnocení celé formy cond (další podmínky se tedy již netestují!). Podmínek může být specifikováno libovolné množství a bývá zvykem u poslední podmínky pouze zapsat T (true), což by například v Javě či C odpovídalo větvi default v konstrukci switch:
(defun sgn (n) (cond ((< n 0) 'negative) ((> n 0) 'positive) ((zerop n) 'zero)))
(print (sgn -10)) negative (print (sgn 0)) zero (print (sgn 10)) positive
Alternativně je možné namísto poslední podmínky vložit T (default):
(defun sgn-2 (n) (cond ((< n 0) 'negative) ((> n 0) 'positive) (t 'zero)))
Počet větví není omezen, takže můžeme velmi elegantně přidat i test, zda je předaná hodnota skutečně číslem:
(defun sgn-3 (n) (cond ((not (numberp n)) 'not-a-number) ((< n 0) 'negative) ((> n 0) 'positive) (t 'zero)))
(print (sgn-3 -10)) negative (print (sgn-3 0)) zero (print (sgn-3 10)) positive (print (sgn-3 nil)) not-a-number
12. Programové smyčky ve funkcionálních jazycích?
Dostáváme se k další vlastnosti klasických LISPů. Tyto jazyky totiž, podobně jako některé další funkcionálně pojaté programovací jazyky, preferují rekurzi před masivním používáním programových smyček. Jsou pro to samozřejmě dobré důvody, jak teoretické, tak i praktické. Ve skutečnosti je však Emacs Lisp jazykem orientovaným na praktické použití, takže ve skutečnosti obsahuje „nefunkcionální“ smyčku while (samozřejmě nejde o příkaz, ale v tomto případě o speciální formu), popř. o makra inspirovaná Common Lispem. Vraťme se však k rekurzi. V Emacs Lispu je většinou možné použít přímý zápis rekurze, tj. v těle vytvářené funkce se může objevit volání této funkce. Zcela typickým příkladem rekurzivní funkce je funkce pro výpočet faktoriálu, jejíž jednoduchá varianta (neochráněná před všemi typy vstupů) může vypadat takto:
(defun factorial (n) (if (<= n 1) 1 (* n (factorial (- n 1))))) (print (factorial 10))
Samozřejmě je možné namísto if použít cond, ovšem v tomto případě k žádnému výraznému zkrácení funkce nedojde:
(defun factorial (n) (cond ((<= n 1) 1) (t (* n (factorial (- n 1))))))
13. Základní programové smyčky založené na speciální formě while
Programové smyčky je možné v jazyku Emacs Lisp vytvářet především s využitím speciální formy nazvané příhodně while. Bude se jednat o univerzální smyčky s testem na začátku; s pomocí této smyčky lze implementovat prakticky libovolnou jinou programovou smyčku. Ukažme si první příklad, v němž je smyčka while použita pro získání sekvence hodnot od 0 do 14. Tyto hodnoty budou tvořit vstup pro výpočet faktoriálu:
(defun factorial (n) (cond ((<= n 1) 1) (t (* n (factorial (- n 1)))))) (setq n 0) (while (< n 15) (print (factorial n)) (setq n (1+ n)))
Ve skutečnosti je možné přepsat i samotný výpočet faktoriálu takovým způsobem, aby se v něm namísto rekurze použila běžná programová smyčka:
(defun factorial (n) (let ((accum 1)) (while (> n 0) (setq accum (* accum n)) (setq n (1- n))) accum)) (setq n 0) (while (< n 15) (print (factorial n)) (setq n (1+ n)))
14. Použití makra dotimes
V některých programech může být poměrně užitečné makro nazvané jednoduše a přitom příhodně dotimes, které dokáže nějaký výraz (formu) opakovat n krát. Přitom toto makro může v každé iteraci (opakování) nastavit zvolenou lokální proměnnou na aktuální hodnotu počitadla, přičemž se hodnota počitadla v první iteraci vždy nastavuje na nulu a v poslední iteraci dosahuje zadaného počtu opakování-1. Vzdáleně tedy můžeme toto makro považovat za ekvivalent programové smyčky for i in range(n): v programovacím jazyku Python či ekvivalent k počítané smyčce for (int i = 0; i<n; i++) známé z céčka (zde bez možnosti mít lokální proměnnou jako počitadlo), C++, Javy atd. Vzhledem k tomu, že se předpokládá, že forma – tělo smyčky – předaná makru dotimes bude mít nějaký vedlejší efekt, nejedná se sice o čistě funkcionální přístup, nicméně makro dotimes může být skutečně velmi užitečné.
V jednoduchém demonstračním příkladu, který si ukážeme, se na standardní výstup vypisuje převrácená hodnota celých čísel od 0 do 9. Vedlejším efektem je v tomto případě samotný výpis na standardní výstup:
(dotimes (i 10) (print (/ 1.0 i)))
S výsledky:
1.0e+INF 1.0 0.5 0.3333333333333333 0.25 0.2 0.16666666666666666 0.14285714285714285 0.125 0.1111111111111111
Podobně můžeme přepsat i výpočet faktoriálu do podoby, která více odpovídá imperativnímu programování:
(defun factorial (n) (setq accumulator 1) (dotimes (value n) (setq accumulator (* accumulator (1+ value)))) accumulator) (dotimes (n 15) (print (factorial n)))
S výsledky:
1 1 2 6 24 120 720 5040 40320 362880 3628800 39916800 479001600 6227020800 87178291200
15. Makro dolist
Makro dolist umožňuje postupně procházet všemi prvky nějakého seznamu. Opět se očekává, že se při průchodu budou volat nějaké funkce s vedlejším efektem (například voláním print), celé použití dolist tak připomíná programové smyčky typu for-each známé z jiných programovacích jazyků.
Podívejme se na velmi jednoduchý příklad s postupným tiskem všech prvků nějakého seznamu:
(setq seznam '(42 'symbol "retezec" nil)) (dolist (prvek seznam) (print prvek))
Poněkud násilně můžeme přepsat i výpočet faktoriálu takovým způsobem, aby se v něm objevily generované seznamy vstupních hodnot:
(defun factorial (n) (setq accumulator 1) (dolist (value (number-sequence 1 n)) (setq accumulator (* accumulator value))) accumulator) (setq inputs (number-sequence 0 14)) (dolist (n inputs) (print (factorial n)))
Opět s výsledky:
1 1 2 6 24 120 720 5040 40320 362880 3628800 39916800 479001600 6227020800 87178291200
16. Interaktivní funkce
Samotné LISPovské funkce jsou skutečně základními stavebními bloky všech modulů naprogramovaných v Emacs Lispu. Ovšem pro jejich zavolání většinou potřebujeme ještě jednu maličkost – možnost vytvořit z funkce příkaz, který se zavolá buď přes klávesovou zkratku M-x jméno-příkazu nebo se namapuje na nějakou uživatelem zvolenou klávesovou zkratku. V Emacs Lispu je nutné toto chování funkcí vynutit uvedením speciální formy interactive. Typicky se této speciální formě předává řetězec, kterým je specifikováno, jakým způsobem se budou nyní již interaktivní funkci-příkazu předávat parametry. V tomto řetězci je možné definovat například:
Znak předaný formě interactive | Význam |
---|---|
b | jméno bufferu |
d | pozice kurzoru (celé kladné číslo) |
f | jméno souboru |
k | sekvence kláves |
n | číslo přečtené z minibufferu |
s | řetězec |
p | prefix zadaný klávesou C-u |
Podívejme se nyní na velmi jednoduchý příklad. Nejdříve opět použijeme definici faktoriálu:
(defun factorial (n) (let ((accum 1)) (while (> n 0) (setq accum (* accum n)) (setq n (1- n))) accum))
Následně nadefinujeme interaktivní funkci, která získá prefix zadaný klávesou C-u:
(defun factorial-comp (n) (interactive "p") (message "The result is %d" (factorial n)))
Postup použití bude následující:
C-u číslo M-x factorial-comp [Enter])
Praktičtější ovšem bude získat číslo z minibuferu:
(defun factorial-comp (n) (interactive "n") (message "The result is %d" (factorial n)))
Po spuštění pomocí:
M-x factorial-comp [Enter])
Bude Emacs čekat na zadání čísla do minibuferu (implicitně oblast vlevo dole) a teprve poté vypočítá a vypíše výsledek.
17. Otestování jednoduché interaktivní funkce
Podívejme se nyní, jakým způsobem je možné použít výše popsanou interaktivní funkci pro výpočet faktoriálu:
Obrázek 1: Výchozí podoba scratch bufferu.
Obrázek 2: Zápis definice obou funkcí přímo ve scratch bufferu.
Obrázek 3: Vložení definice obou funkcí do interpretru Emacs Lispu.
Obrázek 4: Zavolání interaktivní funkce z klávesnice, předání hodnoty 10 přes C-u.
Obrázek 5: Zobrazená odpověď (výsledek).
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:
Všechny příklady kromě posledního se mohou spouštět z příkazové řádky následujícím způsobem:
emacs --script priklad1.el
19. Literatura
- 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
20. Odkazy na Internetu
- 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/