PicoLisp: dokončení popisu a několik praktických rad na závěr

5. 5. 2016
Doba čtení: 15 minut

Sdílet

V závěrečném článku o minimalistickém interpretru jazyka LISP nazvaném PicoLisp si popíšeme práci s vlastnostmi symbolů, použití speciálních symbolů @, @@ a @@@ i využití externích knihoven.

Obsah

1. PicoLisp: dokončení popisu a několik praktických rad na závěr

2. Použití symbolu @

3. Interní reprezentace symbolů

4. Vlastnosti (properties)

5. Přístup k vlastnostem, změna vlastností atd.

6. Reakce na chyby vzniklé při běhu programu

7. Použití externích knihoven

8. Použití interaktivního prostředí PicoLispu

9. Editace s použitím klávesových zkratek Vimu

10. Editace s použitím klávesových zkratek Emacsu

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

12. Příklady pro první článek o PicoLispu

13. Příklady pro druhý článek o PicoLispu

14. Příklady pro třetí článek o PicoLispu

15. Literatura

16. Odkazy na Internetu

1. PicoLisp: dokončení popisu a několik praktických rad na závěr

Na první a druhý článek o minimalistickém interpretru programovacího jazyka LISP pojmenovaného celkem příhodně PicoLisp dnes navážeme a popis tohoto programovacího jazyka dokončíme. Nejdříve si řekneme, jak se při ladění aplikací používá symbol @ (což může být velmi užitečné, a to nejenom ve smyčce REPL), následně se seznámíme se způsobem interní reprezentace symbolů (především řetězců), seznámíme se se způsobem využití externích knihoven a na závěr si řekneme několik praktických rad, které se týkají použití interaktivního prostředí PicoLispu se smyčkou REPL (zajímavé přitom je, že toto prostředí obsahuje podporu pro klávesové zkratky kompatibilní jak s Vimem, tak i s konkurenčním Emacsem). Samozřejmě nesmí chybět ani odkazy na zdrojové kódy demonstračních příkladů.

2. Použití symbolu @

V interaktivní smyčce REPL interpretru PicoLispu je možné využívat symbol pojmenovaný @ (zavináč). Tento symbol obsahuje hodnotu posledního vyhodnoceného výrazu. To například znamená, že není zapotřebí ukládat mezivýsledek nějaké operace do explicitně pojmenované proměnné. Namísto toho se použije @:

(+ 1 2)
3
 
@
3
 
(* @ @)
9

Ve skutečnosti si interpret pamatuje hodnoty tří posledních vyhodnocených výrazů, takže lze použít symboly @, @@ a @@@, přičemž symbol @ obsahuje výsledek posledního výrazu, symbol @@ výsledek výrazu předposledního atd. Podívejme se na příklad:

(+ 1 2)
3
 
(* 6 7)
42
 
(/ 25 5)
5
 
(+ @ @@ @@@)
50

Symbol @ je možné použít i uvnitř řídicích konstrukcí typu if, cond, while atd. V tomto případě obsahuje výsledek řídicího výrazu, tj. například výsledek podmínky. Ten totiž nemusí obsahovat jen hodnotu T či NIL, ale jakoukoli jinou hodnotu (která je většinou automaticky považována za pravdivou). Opět se podívejme na poněkud umělý příklad:

(if (* 6 7) (println @))
42

3. Interní reprezentace symbolů

PicoLispu se rozeznávají čtyři typy symbolů. Prvním typem je samotný NIL, dále se pak jedná o interní symboly, dočasné symboly (transient) a o externí symboly. Symboly jsou interně reprezentovány zcela odlišným způsobem, než například numerické hodnoty. Nejjednodušší symbol je reprezentován tečkovým párem, přičemž první prvek dvojice obsahuje NIL (resp. odkazuje na NIL, což je však ve skutečnosti jedno, protože tento symbol se nemusí dereferencovat) popř. odkaz na vlastnosti (properties) a druhý prvek dvojice obsahuje vlastní hodnotu či odkaz na ni:

       Symbol
            |
            V
      +-----+-----+
      |  /  | VAL |
      +-----+-----+

Interpret při zpracování jednotlivých tečkových párů uložených na haldě dokáže symbol snadno rozpoznat, protože ukazatel na něj vypadá (na 64bitových systémech) následovně:

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx1000

Na 32bitových systémech vypadá ukazatel poněkud odlišně:

xxxxxxxxxxxxxxxxxxxxxxxxxxxxx100

Co to vlastně znamená? Ukazatel v tomto případě obsahuje adresu druhého prvku v tečkovém páru a proto je čtvrtý bit nastaven na jedničku.

4. Vlastnosti (properties)

Užitečné jsou vlastnosti (properties), které mohou být přiřazeny k libovolnému symbolu. Vlastnosti si můžeme představit jako seznam dvojic klíč:hodnota, který je k symbolu přiřazen v prvním prvku tečkového páru (viz též předchozí kapitolu). Pokud existuje alespoň jedna dvojice klíč:hodnota, může odkazovat na další dvojici a tvořit tak rozsáhlejší strukturu. Posledním prvkem této struktury je jméno symbolu. Podívejme se na schéma převzaté z dokumentace k PicoLispu. Zde je vytvořen symbol se jménem „abcdefghijklmno“ (znaky jsou uloženy opačně kvůli způsobu jejich adresování). K tomuto symbolu náleží vlastnosti tvořené dvojicí klíč:hodnota, dále pak samotným klíčem a další dvojicí klíč:hodnota:

            Symbol
            |
            V
      +-----+-----+                                +----------+---------+
      |  |  | VAL |                                |'hgfedcba'|'onmlkji'|
      +--+--+-----+                                +----------+---------+
         | tail                                       ^
         |                                            |
         V                                            | name
         +-----+-----+     +-----+-----+     +-----+--+--+
         |  |  |  ---+---> | KEY |  ---+---> |  |  |  |  |
         +--+--+-----+     +-----+-----+     +--+--+-----+
            |                                   |
            V                                   V
            +-----+-----+                       +-----+-----+
            | VAL | KEY |                       | VAL | KEY |
            +-----+-----+                       +-----+-----+

Poznámka: i řetězce jsou reprezentovány pomocí symbolů. Krátké řetězce o délce maximálně sedmi ASCII znaků mohou být uloženy v jednom tečkovém páru, delší řetězce (15 znaků) ve dvou párech atd. Jazyk PicoLisp je tak jedním z mála programovacích jazyků, v nichž řetězce netvoří kontinuální pole znaků (další výjimkou je Logo, v němž jsou řetězce tvořeny seznamem slov).

5. Přístup k vlastnostem, změna vlastností atd.

Na práci s vlastnostmi není nic složitého: pro nastavení vlastnosti slouží funkce put, pro získání hodnoty vlastnosti (či vlastností) pak funkce get. Opět se podívejme na několik příkladů:

; vytvoříme normální proměnnou (symbol navázaný na hodnotu)
(setq x 42)
42
 
; skutečně jde o proměnnou
x
42
 
; ...běžnou proměnnou s numerickou hodnotou
(* x x)
1764
 
; přidáme k proměnné vlastnost (klíč:hodnota)
(put 'x 'vyznam 'odpoved)
odpoved
 
; stále se jedná o proměnnou
x
42
 
; ...běžnou proměnnou s numerickou hodnotou
(* x x)
1764
 
; na hodnotu vlastnosti se můžeme dotázat
(get 'x 'vyznam)
odpoved
 
; přidáme druhou vlastnost
(put 'x 'vypocet-provedl 'Hlubina-mysleni)
Hlubina-mysleni
 
; přidáme třetí vlastnost (bez hodnoty)
(put 'x 'overeno NIL)
NIL
 
; dotaz na druhou vlastnost
(get 'x 'vypocet-provedl)
Hlubina-mysleni
 
; dotaz na vlastnost bez hodnoty
(get 'x 'overeno)
NIL
 
; dotaz na neexistující vlastnost
(get 'x 'neznama)
NIL

Pokud potřebujete získat celou vlastnost ve formě tečka dvojice (tj. tak, jak je vlastnost uložena na haldě), můžete použít funkci prop:

(prop 'x 'vyznam)
(odpoved . vyznam)
 
(prop 'x 'neznama)
(NIL . neznama)

6. Reakce na chyby vzniklé při běhu programu

V aplikacích naprogramovaných v PicoLispu je možné (i když možná poněkud neobvyklé) používat i řídicí struktury typu try-catch-finally známé spíše z běžných imperativních programovacích jazyků. Tyto struktury jsou realizovány formou (nelokálních) skoků a funkce, které tyto adresy cílů těchto skoků nastavují nebo skoky provádí, se jmenují quit, catch, finally a throw. Následuje několik příkladů na použití těchto funkcí:

Funkce factorial, která dokáže vyhodit výjimku při špatném vstupu. Výjimka je zde pro jednoduchost reprezentována uživatelským symbolem 'negative:

(de factorial
    [n]
    (if (< n 0)
        (throw 'negative)
        (apply * (range 1 n))))
; výpočet, který může skončit s chybou typu 'negative
(catch 'negative
    (finally (println "ukonceni vypoctu") ; větev "finally"
    (for n 10 (println (factorial (- 5 n))))))
 
24
6
2
1
0
"ukonceni vypoctu"

Podívejme se nyní, jakým způsobem je tato konstrukce vytvořena:

  1. Počítáme s tím, že funkce factorial pro záporný argument vyhodí výjimku reprezentovanou uživatelským symbolem 'negative.
  2. Celý kód, v němž může dojít k výjimce, je uzavřen do konstrukcecatch se specifikací výjimky.
  3. Funkce finally obsahuje sekvenci výrazů, které se provedou nezávisle na tom, zda dojde k výjimce či nikoli.
  4. Za finally (ta je dokonce nepovinná) je libovolně dlouhá sekvence výrazů, které se postupně vyhodnocují a případná výjimka se zachytí.
  5. Ve skutečnosti tedy catch spíše odpovídá try v jiných jazycích.

7. Použití externích knihoven

PicoLispu je možné používat dva typy knihoven. Prvním typem jsou knihovny, jejichž zdrojové kódy jsou přímo napsány v PicoLispu. Tyto knihovny mají většinou příponu .l, naleznete je obvykle v adresáři /usr/share/picolisp/lib a načítají se příkazem load. Příkladem může být knihovna „frac.l“ obsahující užitečné funkce použitelné při práci se zlomky::

# 26may11abu
# (c) Software Lab. Alexander Burger
 
(de gcd (A B)
   (until (=0 B)
      (let M (% A B)
         (setq A B B M) ) )
   (abs A) )
 
(de lcm (A B)
   (*/ A B (gcd A B)) )
 
(de frac (N D)
   (if (=0 N)
      (cons 0 1)
      (and (=0 D) (quit "frac/0" N))
      (let G (gcd N D)
         (if (gt0 N)
            (cons (/ N G) (/ D G))
            (cons (- (/ N G)) (- (/ D G))) ) ) ) )
 
(de fabs (A)
   (cons (abs (car A)) (cdr A)) )
 
(de 1/f (A)
   (and (=0 (car A)) (quit "frac/0" A))
   (if (gt0 (car A))
      (cons (cdr A) (car A))
      (cons (- (cdr A)) (- (car A))) ) )
 
(de f+ (A B)
   (let D (lcm (cdr A) (cdr B))
      (let N
         (+
            (* (/ D (cdr A)) (car A))
            (* (/ D (cdr B)) (car B)) )
         (if (=0 N)
            (cons 0 1)
            (let G (gcd N D)
               (cons (/ N G) (/ D G)) ) ) ) ) )
 
(de f- (A B)
   (if B
      (f+ A (f- B))
      (cons (- (car A)) (cdr A)) ) )
 
(de f* (A B)
   (let (G (gcd (car A) (cdr B))  H (gcd (car B) (cdr A)))
      (cons
         (* (/ (car A) G) (/ (car B) H))
         (* (/ (cdr A) H) (/ (cdr B) G)) ) ) )
 
(de f/ (A B)
   (f* A (1/f B)) )
 
(de f** (A N)
   (if (ge0 N)
      (cons (** (car A) N) (** (cdr A) N))
      (cons (** (cdr A) (- N)) (** (car A) (- N))) ) )
 
(de fcmp (A B)
   (if (gt0 (* (car A) (car B)))
      (let Q (f/ A B)
         (*
            (if (gt0 (car A)) 1 -1)
            (- (car Q) (cdr Q))) )
      (- (car A) (car B)) ) )
 
(de f< (A B)
   (lt0 (fcmp A B)) )
 
(de f<= (A B)
   (ge0 (fcmp B A)) )
 
(de f> (A B)
   (gt0 (fcmp A B)) )
 
(de f>= (A B)
   (ge0 (fcmp A B)) )

Druhým typem jsou externí nativní knihovny. Při jejich použití je nutné deklarovat rozhraní mezi lispovskými parametry a parametry nativní funkce. Nativní funkce bez parametrů a bez návratové hodnoty vypadá následovně:

void function(void);

PicoLispu se taková funkce může zavolat příkazem (první parametr je názvem knihovny):

(native "library_name.so" "function")

Je možné přesně specifikovat i hodnoty návratových typů:

(native "library_name.so" "function_name" NIL)                    # void fun(void);
(native "library_name.so" "function_name" 'I)                     # int fun(void);
(native "library_name.so" "function_name" 'N)                     # long fun(void);
(native "library_name.so" "function_name" 'N)                     # void *fun(void);
(native "library_name.so" "function_name" 'S)                     # char *fun(void);
(native "library_name.so" "function_name" 1.0)                    # double fun(void);

A samozřejmě volat i nativní funkce s parametry (povšimněte si automatického přetypování v případě celých čísel):

(native "library_name.so" "function_name" NIL 123)                # void fun(int);
(native "library_name.so" "function_name" NIL 1 2 3)              # void fun(int, long, short);

Podobně je tomu u řetězců:

(native "library_name.so" "function_name" NIL "hello")            # void fun(char*);
(native "library_name.so" "function_name" NIL 42 "world")         # void fun(int, char*);
(native "library_name.so" "function_name" NIL 42 "hello" "world") # void fun(int, char*, char*);

Vzhledem k tomu, že PicoLisp nativně nepodporuje čísla s plovoucí řádovou čárkou, musíme si při volání nativních funkcí akceptujících parametry typu float či double dopomoci malým špinavým trikem:

(native "library_name.so" "function_name" NIL                     # void fun(double, float);
   (12.3 . 1.0) (4.56 . -1.0) )

8. Použití interaktivního prostředí PicoLispu

Podobně jako je tomu u prakticky všech dalších implementací programovacího jazyka LISP či Scheme (popř. i Clojure, které nepochybně patří mezi Lispovské jazyky), je i PicoLisp vybaven interaktivním prostředím se smyčkou REPL (Read-Eval-Print-Loop). V tomto prostředí je možné zadávat jednotlivé výrazy, které jsou ihned vyhodnocovány a výsledky vyhodnocených výrazů jsou zapsány na standardní výstup, což je rozdílné od vyhodnocování výrazů v již hotových programech (skriptech), kde se výsledek výrazu buď předá jiné funkci nebo je zahozen. V interaktivním prostředí je možné využít výše popsané speciální symboly @, @@ a @@@, díky nimž je umožněno se vracet k posledním třem vyhodnoceným výsledkům. Navíc se při inicializaci interaktivního prostředí nahrají všechny základní systémové knihovny včetně knihovny používané pro ladění. Aby skutečně k této inicializaci došlo, je nutné interpret spouštět následujícím způsobem:

pil +

Interaktivní prostředí je vybaveno pamětí již zapsaných řádků (kill ring) a základními schopnostmi editace řádků, podobně jako je tomu v BASHi či podobných aplikacích (ty aplikace, které tuto funkcionalitu přímo nemají, je možné v některých případech „obalit“ pomocí užitečného nástroje rlwrap). Možnost přístupu k historii již zapsaných výrazů a jejich následné editace je velmi užitečná, ovšem je nutné mít na paměti, že PicoLisp není plně kompatibilní s knihovnou GNU Readline, takže není možné použít všechny příkazy, které tato knihovna aplikacím nabízí. Podle preferencí uživatele je možné REPL ovládat příkazy, které jsou kompatibilní s Vimem či s konkurenčním Emacsem. Podívejme se nyní na tyto dvě možnosti podrobněji.

9. Editace s použitím klávesových zkratek Vimu

Ve výchozím nastavení se používá řádkový editor částečně kompatibilní s editory Vi či Vim. To konkrétně znamená, že se při spuštění smyčky REPL editor nachází ve vkládacím režimu (insert mode), v němž pracují pouze základní příkazy. Pro přepnutí do normálního režimu (normal mode) je zapotřebí použít klávesu Esc, podobně jako ve Vi/Vimu. V normálním režimu, tedy po stisku Esc, lze používat klasické příkazy h, j, k, l pro ovládání kurzoru (doleva, doprava, listování historií příkazů) i některé další klávesy, například 0 pro skok na začátek řádku, $ pro skok na konec řádku, D pro vymazání řádku od pozice kurzoru doprava atd. Možnosti jsou však ještě větší, neboť funguje i příkaz f pro vyhledávání znaku (a skok na tento znak) a především lze použít příkaz % pro přeskok na párovou závorku. Tato funkce je v Lispovských jazycích takřka k nezaplacení. Pro přepnutí z normálního režimu do režimu vkládacího slouží zkratky a, i, A a I.

Poznámka: pokud se vám nedaří přepnutí do normálního režimu, může to znamenat, že interaktivní REPL aktuálně používá mód s Emacsovými zkratkami. V tomto případě je nejlepší REPL opustit a z příkazové řádky zadat následující příkaz:

pil -vi +

10. Editace s použitím klávesových zkratek Emacsu

Smyčku REPL lze spustit v režimu částečně kompatibilním s Emacsem takto:

pil -em +

V tomto nastavení se již podle očekávání nerozlišuje mezi normálním a vkládacím režimem, protože se veškeré příkazy zadávají s využitím přeřaďovačů Ctrl nebo Alt (Meta). Mezi základní podporované příkazy samozřejmě patří příkazy pro pohyb kurzoru po textovém řádku, a to jak po znacích, tak i po slovech. Nalezneme zde obvyklé klávesové kombinace Ctrl+B, Alt+B, Ctrl+FAlt+F, ale i příkazy pro přeskok na začátek řádku Ctrl+A a přeskok na konec řádku Ctrl+E. Pro pohyb v historii již dříve zadaných řádků slouží klávesové zkratky Ctrl+P a Ctrl+N, vymazání textu od pozice kurzoru až do konce řádku (užitečná operace) zajišťuje příkaz Ctrl+E. Samotný REPL lze opustit klávesovou zkratkou Ctrl+Q.

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

Všechny demonstrační příklady, které jsme si v trojici článků o PicoLispu popsali, byly uloženy do Git repositáře dostupného na adrese https://github.com/tisnik/pre­sentations. V následujících třech kapitolách naleznete na zdrojové kódy všech demonstračních příkladů přímé odkazy.

bitcoin školení listopad 24

12. Příklady pro první článek o PicoLispu

Odkaz na článek: http://www.root.cz/clanky/picolisp-minimalisticky-a-pritom-prekvapive-vykonny-interpret-lispu/

13. Příklady pro druhý článek o PicoLispu

Odkaz na článek: http://www.root.cz/clanky/picolisp-uzitecne-funkce-a-specialni-formy-pouzivane-pri-tvorbe-aplikaci/

14. Příklady pro dnešní článek o PicoLispu

15. Literatura

  1. Harold Abelson, Gerald Jay Sussman, Julie Sussman:
    Structure and Interpretation of Computer Programs (SICP)
    1985, 1996, MIT Press
  2. Daniel P. Friedman, Matthias Felleisen:
    The Little Schemer
    1995, MIT Press
  3. Daniel P. Friedman, Matthias Felleisen:
    The Seasoned Schemer
    1995, MIT Press
  4. McCarthy:
    „Recursive functions of symbolic expressions and their computation by machine, part I“
    1960
  5. Guy L. Steele:
    „History of Scheme“
    2006, Sun Microsystems Laboratories
  6. Kolář J., Muller K.:
    „Speciální programovací jazyky“
    Praha 1981
  7. „AutoLISP Release 9, Programmer's reference“
    Autodesk Ltd., October 1987
  8. „AutoLISP Release 10, Programmer's reference“
    Autodesk Ltd., September 1988
  9. 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
  10. Carl Hewitt; Peter Bishop and Richard Steiger:
    „A Universal Modular Actor Formalism for Artificial Intelligence“
    1973
  11. Feiman, J.:
    „The Gartner Programming Language Survey (October 2001)“
    Gartner Advisory

16. Odkazy na Internetu

  1. The German School of Lisp
    http://blog.fogus.me/2011/05/03/the-german-school-of-lisp-2/
  2. PicoLisp
    http://picolisp.com/wiki/?home
  3. A PicoLisp Tutorial
    http://software-lab.de/doc/tut.html
  4. Pico Lisp Documentation
    http://picolisp.com/wiki/?Do­cumentation
  5. The PicoLisp Machine
    http://software-lab.de/doc/ref.html#vm
  6. PicoLisp na OpenHubu
    https://www.openhub.net/p/PicoLisp
  7. Pico Lisp: A Case for Minimalist Interpreters?
    http://lambda-the-ultimate.org/node/2124
  8. PicoLisp na Wikipedii
    https://en.wikipedia.org/wi­ki/PicoLisp
  9. Programovací jazyk LISP a LISP machines
    http://www.root.cz/clanky/programovaci-jazyk-lisp-a-lisp-machines/
  10. Programovací jazyk LISP (druhá část)
    http://www.root.cz/clanky/programovaci-jazyk-lisp-druha-cast/
  11. Steel Bank Common Lisp
    http://www.sbcl.org/
  12. CLISP (implementace Common Lispu)
    http://clisp.org/
  13. PLEAC-PicoLisp
    http://pleac.sourceforge.net/ple­ac_picolisp/index.html#AEN4
  14. Rosetta Code – Category:Lisp
    http://rosettacode.org/wi­ki/Category:Lisp
  15. Emacs timeline
    http://www.jwz.org/doc/emacs-timeline.html
  16. EINE (Emacs Wiki)
    http://www.emacswiki.org/emacs/EINE
  17. EINE (Texteditors.org)
    http://texteditors.org/cgi-bin/wiki.pl?EINE
  18. ZWEI (Emacs Wiki)
    http://www.emacswiki.org/emacs/ZWEI
  19. ZWEI (Texteditors.org)
    http://texteditors.org/cgi-bin/wiki.pl?ZWEI
  20. Zmacs (Wikipedia)
    https://en.wikipedia.org/wiki/Zmacs
  21. Zmacs (Texteditors.org)
    http://texteditors.org/cgi-bin/wiki.pl?Zmacs
  22. TecoEmacs (Emacs Wiki)
    http://www.emacswiki.org/e­macs/TecoEmacs
  23. Micro Emacs
    http://www.emacswiki.org/e­macs/MicroEmacs
  24. Micro Emacs (Wikipedia)
    https://en.wikipedia.org/wi­ki/MicroEMACS
  25. EmacsHistory
    http://www.emacswiki.org/e­macs/EmacsHistory
  26. Seznam editorů s ovládáním podobným Emacsu či kompatibilních s příkazy Emacsu
    http://www.finseth.com/emacs.html

Autor článku

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