Úpravy Emacsu s Emacs Lisp: základní konstrukce jazyka

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

Sdílet

Ve druhém článku o Emacs Lispu se naučíme používat prakticky všechny základní konstrukce tohoto jazyka. Ukážeme si deklaraci globálních i lokálních proměnných, rozvětvení, použití rekurze a programových smyček a na závěr si popíšeme i jednoduchou interaktivní funkci.

Obsah

1. Vyhodnocování seznamů

2. Speciální formy

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

14. Použití makra dotimes

15. Makro dolist

16. Interaktivní funkce

17. Otestování jednoduché interaktivní funkce

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

19. Literatura

20. Odkazy na Internetu

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, iflambda 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ů:

  1. čí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)
  2. 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é)
  3. 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)
  4. 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:

  1. podmínku (výraz=formu, která se vyhodnotí na t či nil
  2. formu vyhodnocenou v případě, že je podmínka splněna
  3. formu vyhodnocenou v případě, že podmínka není splněna
Poznámka: z historického hlediska však forma if nebyla v původním LISPu použita. Namísto ní mohli programátoři použít obecnější formu nazvanou cond, kterou samozřejmě taktéž máme v Emacs Lispu k dispozici. Další dvě speciální formy, které samy o sobě budou postačovat k tvorbě plnohodnotného LISPu, již známe – quote a lambda. Pokud přidáte pětici funkcí atom, eq, car, cdr a cons, získáte LISP ve zhruba takové podobě, v jaké byl poprvé prakticky implementován.

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."
Poznámka: v případě použití setq není možné dokumentační řetězec zadat, protože by interpret nevěděl, kde začíná definice další proměnné.

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
Poznámka: u některých dialektů LISPu nalezneme odlišné chování. Například v Clojure postačuje použít let:
(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
Poznámka: ve skutečnosti nejsou formy and a or obyčejnými funkcemi, ale speciálními formami. Je tomu tak z toho důvodu, aby bylo podporováno zkrácené vyhodnocování výrazů (argumenty jsou vyhodnocovány zprava doleva, ovšem jen do toho okamžiku, kdy již není zcela zřejmý výsledek, tj. například u and do první nepravdivé hodnoty). Proto taky můžeme v některých případech and a or použít namísto podmínky if (záleží na tom, zda a jak potřebujeme zpracovat výslednou hodnotu):
(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
Poznámka: zvláštnost symbolu nil podtrhuje i to, že se současně jedná o atom i o seznam. Je tomu díky „dualitě“ tohoto symbolu, který odpovídá prázdnému seznamu. Totéž tím pádem platí i pro prázdný seznam!

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)))
Poznámka: pro zjednodušení se na formu progn můžeme dívat jako na programový blok v jiných programovacích jazycích, přičemž posledním příkazem je return.

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))
Poznámka: v některých LISPech nalezneme formu nazvanou if2. Této formě se nepředává jedna podmínka, ale hned dvě podmínky, které jsou vždy obě vyhodnoceny. Výsledkem je tedy kombinace dvou konstant t a nil (celkem čtyři možnosti). Proto také za oběma podmínkami následují čtyři výrazy, přičemž první výraz se vyhodnotí jen pokud jsou obě podmínky splněny (t t), druhý výraz se vyhodnotí, jen když je splněna první podmínka (t nil), třetí při splnění pouze druhé podmínky (nil t) a konečně čtvrtý výraz při nesplnění obou podmínek současně (nil nil).

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.

Poznámka: interaktivním funkcím bude věnován samostatný článek.

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

bitcoin_skoleni

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

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

# Demonstrační příklad Popis Cesta
1 01_varibles.el použití globálních proměnných https://github.com/tisnik/elisp-examples/blob/master/elisp-2/01_varibles.el
2 02_variable_docstrings.el dokumentační řetězce https://github.com/tisnik/elisp-examples/blob/master/elisp-2/02_variable_docstrings.el
3 03_let.el forma let a lokální proměnné https://github.com/tisnik/elisp-examples/blob/master/elisp-2/03_let.el
4 04_parallel_let.el paralelní let https://github.com/tisnik/elisp-examples/blob/master/elisp-2/04_parallel_let.el
5 05_sequential_let.el sekvenční let* https://github.com/tisnik/elisp-examples/blob/master/elisp-2/05_sequential_let.el
6 06_boolean_ops.el Booleovské operace https://github.com/tisnik/elisp-examples/blob/master/elisp-2/06_boolean_ops.el
7 07_predicates.el základní predikáty https://github.com/tisnik/elisp-examples/blob/master/elisp-2/07_predicates.el
8 08_control_structures.el řídicí struktury Elispu https://github.com/tisnik/elisp-examples/blob/master/elisp-2/08_control_structures.el
9 09_cond.el použití speciální formy cond https://github.com/tisnik/elisp-examples/blob/master/elisp-2/09_cond.el
10 10_cond2.el použití speciální formy cond https://github.com/tisnik/elisp-examples/blob/master/elisp-2/10_cond2.el
11 11_factorial_cond.el výpočet faktoriálu používající cond https://github.com/tisnik/elisp-examples/blob/master/elisp-2/11_factorial_cond.el
12 12_factorial_while.el výpočet faktoriálu používající while https://github.com/tisnik/elisp-examples/blob/master/elisp-2/12_factorial_while.el
13 13_dotimes.el makro dotimes https://github.com/tisnik/elisp-examples/blob/master/elisp-2/13_dotimes.el
14 14_factorial_dotimes.el výpočet faktoriálu s využitím dotimes https://github.com/tisnik/elisp-examples/blob/master/elisp-2/14_factorial_dotimes.el
15 15_dolist.el makro dolist https://github.com/tisnik/elisp-examples/blob/master/elisp-2/15_dolist.el
16 16_factorial_dolist.el výpočet faktoriálu s využitím dolist https://github.com/tisnik/elisp-examples/blob/master/elisp-2/16_factorial_dolist.el
17 17_factorial_interactive.el interaktivní funkce https://github.com/tisnik/elisp-examples/blob/master/elisp-2/17_factorial_interactive.el

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

  1. McCarthy
    „Recursive functions of symbolic expressions and their computation by machine, part I“
    1960
  2. Guy L. Steele
    „History of Scheme“
    2006, Sun Microsystems Laboratories
  3. Kolář J., Muller K.:
    „Speciální programovací jazyky“
    Praha 1981
  4. „AutoLISP Release 9, Programmer's reference“
    Autodesk Ltd., October 1987
  5. „AutoLISP Release 10, Programmer's reference“
    Autodesk Ltd., September 1988
  6. McCarthy, John; Abrahams, Paul W.; Edwards, Daniel J.; Hart, Timothy P.; Levin, Michael I.
    „LISP 1.5 Programmer's Manual“
    MIT Press. ISBN 0 262 130 1 1 4
  7. Carl Hewitt; Peter Bishop and Richard Steiger
    „A Universal Modular Actor Formalism for Artificial Intelligence“
    1973
  8. Feiman, J.
    „The Gartner Programming Language Survey (October 2001)“
    Gartner Advisory
  9. Harold Abelson, Gerald Jay Sussman, Julie Sussman:
    Structure and Interpretation of Computer Programs
    MIT Press. 1985, 1996 (a možná vyšel i další přetisk)
  10. Paul Graham:
    On Lisp
    Prentice Hall, 1993
    Dostupné online na stránce http://www.paulgraham.com/on­lisptext.html

20. Odkazy na Internetu

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

Autor článku

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