Jazyky pro umělou inteligenci

Podobné dokumenty
FUNKCIONÁLNÍ A LOGICKÉ PROGRAMOVÁNÍ 3. CVIČENÍ

LISP Definice funkcí

Programovací jazyk Pascal

Funkcionální programování úvod

Prolog PROgramming in LOGic část predikátové logiky prvního řádu rozvoj začíná po roce 1970 Robert Kowalski teoretické základy Alain Colmerauer, David

Paradigmata programování 1

Hanojská věž. T2: prohledávání stavového prostoru. zadání [1 1 1] řešení [3 3 3] dva možné první tahy: [1 1 2] [1 1 3]

Čtvrtek 8. prosince. Pascal - opakování základů. Struktura programu:

Algoritmizace prostorových úloh

Paradigmata programování 1

Slepé prohledávání do šířky Algoritmus prohledávání do šířky Při tomto způsobu prohledávání máme jistotu, že vždy nalezneme koncový stav, musíme ale p

1. Od Scheme k Lispu

PARADIGMATA PROGRAMOVÁNÍ 2 PŘÍSLIBY A LÍNÉ VYHODNOCOVÁNÍ

Funkcionální programování. Kristýna Kaslová

Logické programování I

Programovací jazyky. imperativní (procedurální) neimperativní (neprocedurální) assembler (jazyk symbolických instrukcí)

Logika a logické programování

Algoritmizace a programování

4.2 Syntaxe predikátové logiky

Programovací jazyky. imperativní (procedurální) neimperativní (neprocedurální) assembler (jazyk symbolických instrukcí)

Booleovská algebra. Booleovské binární a unární funkce. Základní zákony.

Vyučovací hodina. 1vyučovací hodina: 2vyučovací hodiny: Opakování z minulé hodiny. Procvičení nové látky

Paradigmata programování 1

Maturitní otázky z předmětu PROGRAMOVÁNÍ

popel, glum & nepil 16/28

Základy umělé inteligence

Základní způsoby: -Statické (přidělění paměti v čase překladu) -Dynamické (přiděleno v run time) v zásobníku na haldě

MATURITNÍ OTÁZKY ELEKTROTECHNIKA - POČÍTAČOVÉ SYSTÉMY 2003/2004 PROGRAMOVÉ VYBAVENÍ POČÍTAČŮ

Negativní informace. Petr Štěpánek. S použitím materiálu M.Gelfonda a V. Lifschitze. Logické programování 15 1

ALGORITMIZACE A PROGRAMOVÁNÍ

5 Přehled operátorů, příkazy, přetypování

Výroková a predikátová logika - II

Algoritmizace prostorových úloh

Algoritmizace a programování

Paradigmata programování 1

Seminární práce z Teorie ICT

Obsah přednášky. programovacího jazyka. Motivace. Princip denotační sémantiky Sémantické funkce Výrazy Příkazy Vstup a výstup Kontinuace Program

Základní způsoby: -Statické (přidělění paměti v čase překladu) -Dynamické (přiděleno v run time) v zásobníku na haldě

Výroková a predikátová logika - II

FUNKCIONÁLNÍ A LOGICKÉ PROGRAMOVÁNÍ 5. CVIČENÍ

Algoritmus. Přesné znění definice algoritmu zní: Algoritmus je procedura proveditelná Turingovým strojem.

Logické operace. Datový typ bool. Relační operátory. Logické operátory. IAJCE Přednáška č. 3. může nabýt hodnot: o true o false

Maturitní témata. IKT, školní rok 2017/18. 1 Struktura osobního počítače. 2 Operační systém. 3 Uživatelský software.

3 Co je algoritmus? Trocha historie Definice algoritmu Vlastnosti algoritmu... 3

Operátory, výrazy. Tomáš Pitner, upravil Marek Šabo

Výroková logika. Teoretická informatika Tomáš Foltýnek

6 Příkazy řízení toku

Lekce 01 Úvod do algoritmizace

Matematická logika. Rostislav Horčík. horcik

Výroková a predikátová logika - V

IB015 Neimperativní programování. Organizace a motivace kurzu, programovací jazyk Haskell. Jiří Barnat

8 Třídy, objekty, metody, předávání argumentů metod

PODPROGRAMY PROCEDURY A FUNKCE

Modely Herbrandovské interpretace

Masarykova střední škola zemědělská a Vyšší odborná škola, Opava, příspěvková organizace

Unární je také spojka negace. pro je operace binární - příkladem může být funkce se signaturou. Binární je velká většina logických spojek

Výroková a predikátová logika - II

Logika. 2. Výroková logika. RNDr. Luděk Cienciala, Ph. D.

Struktura programu v době běhu

Přednáška 3. Rekurze 1

Paměť počítače. alg2 1

Paradigmata programování 2

Výroková logika - opakování

Vyhledávání. doc. Mgr. Jiří Dvorský, Ph.D. Katedra informatiky Fakulta elektrotechniky a informatiky VŠB TU Ostrava. Prezentace ke dni 21.

Predikátová logika. prvního řádu

5 Orientované grafy, Toky v sítích

Databázové systémy. * relační kalkuly. Tomáš Skopal. - relační model

2.1 Podmínka typu case Cykly Cyklus s podmínkou na začátku Cyklus s podmínkou na konci... 5

Algoritmizace prostorových úloh

Programování v čistém Prologu

Prohledávání do šířky = algoritmus vlny

Reprezentace aritmetického výrazu - binární strom reprezentující aritmetický výraz

teorie logických spojek chápaných jako pravdivostní funkce

NPRG030 Programování I, 2016/17 1 / :58:13

Přednáška 7. Celočíselná aritmetika. Návratový kód. Příkazy pro větvení výpočtu. Cykly. Předčasné ukončení cyklu.

Formální systém výrokové logiky

Matematická logika. Miroslav Kolařík

1. lekce. do souboru main.c uložíme následující kód a pomocí F9 ho zkompilujeme a spustíme:

Lisp 7-1. opakování destruktivních změn seznamů

PARADIGMATA PROGRAMOVÁNÍ 2A INTERPRET S VEDLEJŠÍMI EFEKTY A MAKRY

Vektory a matice. Obsah. Aplikovaná matematika I. Carl Friedrich Gauss. Základní pojmy a operace

Výrazy a operátory. Operátory Unární - unární a unární + Např.: a +b

Da D to t v o é v ty t py IB111: Datové typy

Rekurzivní algoritmy

Program a životní cyklus programu

NPRG030 Programování I, 2010/11

Sémantika výrokové logiky. Alena Gollová Výroková logika 1/23

Vlastnosti algoritmu. elementárnost. determinovanost. rezultativnost. konečnost. hromadnost. efektivnost

1.1 Struktura programu v Pascalu Vstup a výstup Operátory a některé matematické funkce 5

Datové struktury 2: Rozptylovací tabulky

Matematická logika. Miroslav Kolařík

platné nejsou Sokrates je smrtelný. (r) 1/??

IB112 Základy matematiky

Základní pojmy. Úvod do programování. Základní pojmy. Zápis algoritmu. Výraz. Základní pojmy

Programovací jazyk Prolog

Assembler - 5.část. poslední změna této stránky: Zpět

Algoritmizace. Obrázek 1: Přeložení programu překladačem

Úvod do informatiky. Miroslav Kolařík

Tabulkový procesor. Základní rysy

Algoritmizace. 1. Úvod. Algoritmus

Transkript:

VYSOKÉ UČENÍ TECHNICKÉ V BRNĚ FAKULTA STROJNÍHO INŽENÝRSTVÍ Ústav automatizace a informatiky Jiří Dvořák 2005

Obsah 1. Úvod......... 4 1.1 Funkcionální programování....... 4 1.2 Logické programování...... 7 2. Jazyk Lisp..... 9 2.1 Typy dat.. 9 2.2 Struktura programu. 12 2.3 Základní funkce...... 12 2.4 Aritmetické a relační operace...... 16 2.5 Definice funkcí...... 16 2.6 Funkce pro pracování seznamů 19 2.7 Predikátové funkce.. 20 2.8 Vyhodnocovací funkce... 23 2.9 Řídicí programové struktury... 24 2.10 Mapovací funkcionály...... 28 2.11 Definice a vyhodnocení maker....... 29 2.12 Modifikace struktur. 31 2.13 Reprezentace atomů.... 33 2.14 Vstup a výstup. 35 3. Využití jazyka Lisp pro řešení úloh umělé inteligence... 37 3.1 Přehled aplikací.... 37 3.2 Příklad využití Lispu pro tvorbu expertního systému.. 38 4. Jazyk Prolog... 42 4.1 Charakteristika a příklad programu. 42 4.2 Typy a syntaxe dat... 48 4.3 Syntaxe programu 49 4.4 Unifikace.. 50 4.5 Deklarativní a procedurální sémantika... 51 4.6 Blokový model vyhodnocování... 54 4.7 Zpracování čísel....... 56-2 -

4.8 Zpracování seznamů.... 58 4.9 Operátorová notace.. 61 4.10 Typové predikáty. 63 4.11 Rozklad a vytváření termů.. 64 4.12 Meta-logické predikáty.... 65 4.13 Řez... 67 4.14 Negace. 68 4.15 Práce s databází... 68 4.16 Vstup a výstup. 70 4.17 Cykly... 71 5. Využití jazyka Prolog pro řešení úloh umělé inteligence... 73 5.1 Přehled aplikací... 73 5.2 Příklad využití Prologu pro tvorbu expertního systému.. 73 Literatura. 78 Seznam internetových adres... 79 Přílohy Příloha A: Přehled definovaných jmen Lispu.. 80 Příloha B: Přehled definovaných jmen a operátorů Prologu 81 Příloha C: Výsledky úloh na procvičení.. 82-3 -

1. Úvod Řešení problémů umělé inteligence (UI) lze naprogramovat i v běžných programovacích jazycích, jako je Pascal nebo C. Vzhledem k charakteru problémů UI jsou pro tento účel často používány speciální programovací jazyky, jako např. Lisp (používaný zejména v USA) a Prolog (populární zejména v Evropě). Speciální programovací jazyky pro UI jsou obvykle založeny na jiných paradigmatech programování (programovacích stylech) než klasické programovací jazyky. Programovací styly Niklaus Wirth (autor jazyka Pascal) zformuloval známou rovnici Program = algoritmus + datové struktury Robert Kowalski (jeden za zakladatelů logického programování) tuto rovnici doplnil takto: Algoritmus = funkce (logika) + řízení Podle toho, na kterou složku algoritmu je kladen větší důraz, můžeme rozlišit dva hlavní programovací styly: Imperativní programovací styl: Je kladen důraz na řídicí složku, tj. JAK se má výpočet provést. Program je tvořen posloupností příkazů. Neimperativní (deklarativní) programovací styly: Řídicí složka je potlačena a důraz je kladen na to CO má být vypočteno. Program je např. tvořen souborem definic funkcí a jejich aplikací ve formě výrazů (funkcionální programování) nebo souborem logických formulí (logické programování), které specifikují řešený problém. Významným rysem deklarativních jazyků (v jejich čisté podobě) je absence přiřazovacího příkazu. Bez tohoto příkazu mohou programátoři pouze deklarovat, jaké účinky by měly vést k jakým výsledkům. Deklarativní jazyky můžeme charakterizovat jako jazyky s referenční transparentností. Referenční transparentnost znamená omezení rozsahu, ve kterém mohou programátoři měnit hodnoty proměnných. V rámci neimperativních programovacích stylů hrají důležitou roli tyto dva styly: Funkcionální styl: základní komponentou programu je funkce. Logický styl: základní komponentou programu je relace. Použití neimperativních jazyků: Řešení speciálních problémů (např. problémů umělé inteligence) Vytvoření prototypu programu, po jehož ověření se může pokračovat imperativním způsobem 1.1 Funkcionální programování Funkcionální program se vytváří jako souhrn definic funkcí a jejich aplikací ve formě výrazů. Jedná se o to, jak vyjádřit daný algoritmus pomocí vzájemně provázaných (zpravidla rekurzivních) funkcí. Výpočet podle takového programu lze chápat jako provedení aplikace nějaké funkce na určitou sadu hodnot jejích argumentů s cílem získat výslednou hodnotu. Počítač - 4 -

má ve funkcionálním programování roli vyhodnocovače výrazů přičemž základním krokem vyhodnocení je zjednodušení (redukce) výrazu. Výsledek výpočtu do značné míry nezávisí na pořadí, v němž se redukují jednotlivé dílčí výrazy. To vede ke snížení rizika možných chyb programování a umožňuje paralelní provádění redukčních transformací na víceprocesorových počítačích. Principy funkcionálního programování mohou také vést k navrhování počítačů, jejichž technická realizace už nemusí vycházet z von Neumannova modelu. Definice Výrazy Prostředí Vyhodnocení Výsledky Obr. 1.1. Počítač jako vyhodnocovač výrazů Výhody funkcionálních programovacích jazyků lze shrnout do těchto bodů: dovolují psát kratší a elegantnější programy než v konvenčních jazycích; dovolují psát programy, které vypadají jako specifikace; automatické transformační systémy je pak mohou konvertovat do účinně fungujících programů; funkcionální programy mohou snadno využít paralelismu multiprocesorových systémů. Funkcionální jazyky nahrazují statické datové struktury dynamickými datovými strukturami, kde paměť pro nějakou položku je (automaticky) alokována teprve v okamžiku, kdy položka začíná existovat. Příklady funkcionálních programovacích jazyků: Lisp - 5 -

FP Hope Miranda [21] Haskell Při striktním pohledu není možno jazyk Lisp považovat za čistě funkcionální jazyk, protože do různých verzí Lispu byly přidány početné imperativní rysy. Problematikou funkcionálního programování se zabývá např. kniha [7]. Rekurze V neimperativním programovacím stylu je rekurze důležitým nástrojem pro řešení složitých problémů. Taxonomie rekurzivních definic: Vnořená rekurze: rekurzivní volání funkce obsahuje v argumentech volání téže funkce. Příklad: Ackermannova funkce y + 1 ( x = 0) ack ( x, y) = ack( x 1, 1) ( y = 0) ack( x 1, ack( x, y 1)) ( jinak) Kaskádní (stromová) rekurze: ve stejném výrazu se objevuje několik rekurzivních volání, ale bez vzájemného vnoření. Příklad: Funkce pro určení n-tého prvku Fibonacciho posloupnosti n fib ( n) = fib( n 2) + ( n 1) fib( n 1) ( jinak) Lineární rekurze: v každé z alternativ v definici funkce se vyskytuje nejvýše jedno její rekurzivní volání. Příklad: Funkce pro výpočet faktoriálu přirozeného čísla n 1 ( n = 0) fakt ( n) = n * fakt( n 1) ( jinak) Koncová rekurze: speciální případ lineární rekurze, kdy rekurzivní volání je poslední operací příslušné alternativy definice. Příklad: funkce pro výpočet největšího společného dělitele dvou celých kladných čísel x nsd ( x, y) = nsd( x y, y) nsd( x, y x) Příklady převodu definice faktoriálu na koncovou rekurzi: ( x = y) ( x > y) (jinak) - 6 -

a) fakt ( n) = fakt1(1, n) m fakt1( m, i) = fakt1( m * i, i 1) ( i = 0) ( i > 0) b) fakt ( n) = fakt2(1, n, 1) m fakt2( m, n, i) = fakt2( m * i, n, i + 1) ( i > n) ( i n) 1.2 Logické programování Při logickém programování těžiště práce tvůrce programu spočívá v nalezení vhodného popisu řešené úlohy a vztahů v ní. Typickým reprezentantem jazyků pro logické programování je jazyk Prolog. Tento jazyk vychází z toho, že pokud máme k dispozici co nejpřesnější popis všech důležitých vlastností světa úlohy, pak je možné využít metod automatického dokazování vět pro nalezení posloupnosti akcí, které povedou k řešení úlohy. Systém automatického dokazování vět tak přebírá roli překladače logického programu a jeho úkolem je navrhnout vhodné zřetězení základních operací počítače pro vyřešení zadané úlohy. Výchozím vyjadřovacím jazykem logického programování je jazyk logiky prvního řádu. Objekty světa jsou v tomto jazyce popisovány pomocí termů, které reprezentují konstanty, proměnné a případně i složené termy vzniklé aplikací funkcí na termy jednodušší. Proměnná je jméno, které zastupuje neznámý objekt splňující formuli, v níž se vyskytuje. Tento objekt může být postupně upřesňován pomocí substituce v průběhu výpočtu. Často se zkoumá, zda dva výrazy logického jazyka lze ztotožnit tím, že se provede vhodná substituce za proměnné v obou výrazech (říkáme, že se hledá unifikující substituce resp. unifikace). Vztahy mezi objekty popisují predikáty, které odpovídají relacím. Prolog je postaven na lineární strategii rezoluce, která se stala jeho hlavním odvozovacím prostředkem. Tato strategie je však úplná jen pro jistou třídu klauzulí, označovanou jako Hornovy klauzule. Klauzule je libovolná konečná disjunkce literálů (atomů) a v Hornově klauzuli se nesmí objevit více než jeden pozitivní atom. Tím jsou poněkud zúženy vyjadřovací možnostmi jazyka Prolog ve srovnání s jazykem predikátové logiky 1. řádu v plné obecnosti. Je-li toto omezení plně respektováno, říkáme, že jde o čistý Prolog. Pro praktické účely se zavádějí různá vhodná mimologická rozšíření. Při přechodu od malých prototypů k rozsáhlým uživatelsky orientovaným systémům naráží použití standardního Prologu na bariéry, způsobené nadměrnými časovými a paměťovými nároky. Tyto problémy sice zčásti řeší kompilační verze překladačů Prologu, ale přesto je programátor často nucen se odchylovat od čistě deklarativního přístupu. Jiným problémem standardního Prologu je unifikace, která dovoluje srovnávat pouze syntakticky příbuzné objekty. Při řešení praktických úloh by však často bylo výhodná možnost srovnávat i objekty sémanticky ekvivalentní. Řešení výše uvedených problémů nabízí nová programovací metoda logické programování s omezujícími podmínkami (Constraint Logic Programming CLP). V CLP je unifikace nahrazena rozhodovací procedurou, která umožňuje využívat jak syntaxi, tak i sémantiku - 7 -

zpracovávaných objektů. Základem této procedury je řešení omezujících podmínek (constraint satisfaction). CLP není pouhým rozšířením standardního Prologu, ale jeho zobecněním. Výhody logického programování lze shrnout takto: Logické programování je deklarativní, neboť výsledná posloupnost elementárních akcí vedoucích k cíli je odvozena automaticky bez zásahu programátora. Vzhledem k této vlastnosti je snadné rozšířit logický program o nové informace, datové struktury a pravidla, jak s nimi zacházet. Při použití logických programovacích jazyků je výsledný programový kód krátký a přehledný, což usnadňuje jeho ladění a udržování. Jazyky logického programování mají bohaté prostředky pro konstrukci a definici různých datových struktur. Uživatel má bezprostřední přístup k prvkům složitých struktur bez nutnosti používat ukazatel (pointer). Navíc operace unifikace a zpětného chodu (backtracking) usnadňují srovnávání vzorů a prohledávání prostoru možností. Uvedené vlastnosti podporují metaprogramování, kdy se s programy zachází jako s daty. Příklady jazyků pro logické programování: Prolog ECL i PS e Gödel Mercury - 8 -

2. Jazyk Lisp Jazyk Lisp je jedním z nejstarších dosud používaných programovacích jazyků. Původně vznikl koncem 50. let (Mc Carthy, 1960) jako určitá notace pro zápis algoritmů pracujících se seznamy (LISP = LISt Processing). Verze jazyka Lisp: Lisp 1.5 (Mc Carthy, 1962) MacLisp [24] InterLisp Franz Lisp [22] Zeta Lisp Scheme [8] Common Lisp [12] Xlisp V této kapitole jsou popsány pouze základy jazyka Lisp, přičemž se orientujeme zejména na Common Lisp. Podrobněji se s jazykem Lisp můžete seznámit v knihách [10], [12], [18], [22], [24]. 2.1 Typy dat Souhrnně se data v Lispu nazývají S-výrazy (symbolické výrazy). Dělíme je na jednoduché a strukturované. Další dělení ukazuje obr. 2.1. S-výrazy jednoduché (atomy) strukturované čísla symboly tečka-dvojice seznamy celá reálná speciální znaky identifikátory Obr. 2.1. Typy dat v Lispu - 9 -

Atomy Atomy jsou základní syntaktickou jednotkou jazyka a zahrnují jednak čísla (celá nebo reálná), jednak symboly (speciální znaky a identifikátory). Příklady atomů: čísla: 123-5.46 speciální znaky: + - * < > = identifikátory. alfa Beta1 pocet_atomu ponekud-delsi-atom Identifikátory mohou obsahovat písmena, číslice, pomlčku a podtržítko. Obvykle se nerozlišují velká a malá písmena. Tečka-dvojice (S-výraz1.S-výraz2) Tečka-dvojice představuje binární strom, kde S-výraz1 odpovídá levému podstromu a S-výraz2 pravému podstromu. Tečka-dvojice je fyzicky reprezentována základní lispovskou buňkou (tzv. cons-buňkou), dělenou na 2 části, které obsahují ukazatele na příslušné podvýrazy (viz obr. 2.2). Názvy těchto částí jsou dány historicky (označují části strojového slova na počítači IBM 1040, kde byl Lisp poprvé implementován). address part decrement part S-výraz1 S-výraz2 Obr. 2.2. Fyzická reprezentace tečka-dvojice Příklady tečka-dvojic: (A.B) ((A.B).C) A B C A B Obr. 2.3. Příklady reprezentace tečka-dvojic - 10 -

Seznam (S-výraz1 S-výraz2 S-výrazN) Seznam je tvořen posloupností S-výrazů oddělených mezerami a uzavřených do kulatých závorek. Seznam je vlastně speciální případ tečky-dvojice zapsané ve zkrácené notaci. Zápis seznamu v podobě tečky-dvojice: (S-výraz1.(S-výraz2.(.(S-výrazN.nil) ))) Atom nil je speciální atom, který reprezentuje konec seznamu. Fyzickou implementaci seznamu ukazuje obr. 2.4. S-výraz1 S-výraz2 S-výrazN Obr. 2.4. Fyzická reprezentace seznamu Příklady: seznamová notace tečka notace (A) (A.nil) (A B C) (A.(B.(C.nil))) (A (B C) D) (A.((B.(C.nil)).(D.nil))) Prázdný seznam ( ) je ekvivalentní atomu nil. A B C A D B C Obr. 2.5. Příklady reprezentace seznamů - 11 -

2.2 Struktura programu Program v Lispu se skládá z tzv. forem. Forma (E-výraz) je S-výraz, který lze vyhodnotit lispovským interpretem. Program je tedy posloupností vyhodnotitelných S-výrazů, což znamená, že program má stejný charakter jako jím zpracovávaná data. Typy forem: Číselný atom vyhodnocením se získá číslo vyjádřené zápisem atomu. Symbolický atom (identifikátor) Zápis funkce Symbolický atom (identifikátor) v pozici formy Jestliže se symbolický atom nachází v pozici formy, je považován za proměnnou a jeho vyhodnocením se získá hodnota, která je v daném okamžiku vázána na daný atom. Pokud v okamžiku vyhodnocení není na daný atom žádná hodnota navázána, je signalizována chyba. Proměnné se standardně přiřazenou hodnotou: T hodnota je T (true) NIL hodnota je NIL F hodnota je NIL Pozn.: platí konvence, že jakákoli hodnota různá od NIL vyjadřuje pravdivostní hodnotu true. Zápis funkce Zápis funkce (přesněji zápis aplikace funkce) má tvar seznamu (f a1 a2 an) kde f je jméno (symbolický atom) vyjadřující funkci a a1, a2,, an jsou formy vyjadřující argumenty, na něž se má funkce f aplikovat. Pro zápis funkce se v Lispu používá důsledně prefixová notace, přičemž se celý zápis uzavírá vždy do závorek. Tento požadavek se týká i zápisu aritmetických a relačních výrazů. Při vyhodnocení zápisu funkce Lisp nejprve vyhodnotí všechny argumenty a teprve na získané hodnoty aplikuje funkci f. Pořadí vyhodnocování argumentů není v Lispu přesně specifikováno. Výjimkou z tohoto pravidla jsou speciální funkce, z nichž každá má specifická pravidla pro vyhodnocení (resp. nevyhodnocení) argumentů. Zápisy těchto funkcí se nazývají speciální formy. 2.3 Základní funkce Speciální funkce quote (quote S-výraz) Funkce quote představuje speciální identitu: jejím argumentem může být libovolný S-výraz, který se nevyhodnocuje a funkce jej vrátí jako svůj výsledek. - 12 -

Příklad (za šipkou je uvedena výsledná hodnota): (quote (a b)) (a b) Tato identita se může zkráceně zapsat pomocí apostrofu podle následujícího vztahu 'S-výraz (quote S-výraz) Tedy místo (quote (a b)) můžeme napsat '(a b). Použití apostrofu před číselným argumentem je ovšem zbytečné. Speciální funkce setq (setq proměnná forma) Funkce setq je analogií příkazu přiřazení z imperativních jazyků. Prvý argument funkce setq je proměnná, která se nevyhodnocuje. Druhý argument je forma, která se vyhodnotí a její hodnota se naváže na uvedenou proměnnou. Tato hodnota je také výsledkem aplikace funkce setq. Příklady: (setq X 0) 0 X 0 (setq Y '(a b c)) (a b c) Y (a b c) Pokud bychom opomenuli apostrof před seznamem (a b c), systém by se jej pokusil vyhodnotit jako aplikaci funkce a, přičemž b a c by chápal jako proměnné. Pomocí setq je možné zajistit vazbu více proměnných. Vyhodnocování a navazování se v tomto případě provádí postupně. Výslednou hodnotou je hodnota poslední formy. Příklad: (setq prom1 forma1... (setq A 'B A B Funkce car a cdr promn forman) B 'C) C (car forma) B C Hodnotou argumentu musí být tečka-dvojice a hodnotou funkce car je pak první prvek této dvojice. Při aplikaci na neprázdný seznam dostaneme první prvek seznamu. CAR znamená Contents of Address Register. - 13 -

(cdr forma) Hodnotou argumentu musí být tečka-dvojice a hodnotou funkce cdr je pak druhý prvek této dvojice. Při aplikaci na neprázdný seznam dostaneme zbytek seznamu. CDR znamená Contents of Decrement Register. Pozn.: Tyto funkce nemají destruktivní charakter původní S-výraz zůstává zachován. Příklady: (car '(A)) (cdr '(A)) (car '(A.B)) (cdr '(A.B)) (car '(A B)) (cdr '(A B)) A nil A B A (B) (car '(A B C)) A (cdr '(A B C)) (B C) Existují také složené funkce tvaru cxx xr, kde místo x může být a (zastupuje car) nebo d (zastupuje cdr). Příslušné funkce se aplikují zprava doleva. Platí např. následující identity: Funkce cons (caar X) (car (car X)) (cadr X) (car (cdr X)) (cons forma1 forma2) Hodnotami argumentů mohou být libovolné S-výrazy. Hodnotou funkce cons je tečka-dvojice vytvořená z prvého a druhého S-výrazu. Provedení funkce cons znamená vytvoření nové consbuňky, obsahující ukazatele na buňky reprezentující uvedené S-výrazy. Příklady: (cons 'A 'B) (A.B) (cons 'A '(B)) (A B) (cons '(A B) '(C D)) ((A B) C D) (car (cons 'A '(B C))) A (cdr (cons 'A '(B C))) (B C) (setq L '(A B)) (A B) (cons (car L) (cdr L)) (A B) - 14 -

Funkce atom a eq (atom forma) Hodnotou argumentu může být libovolný S-výraz. Funkce atom testuje, zda tato hodnota je atom, tj. číslo nebo symbol, a v tom případě má hodnotu T. V opačném případě má tato funkce hodnotu nil. (eq forma1 forma2) Hodnotami argumentů musejí být atomy. Funkce eq testuje, zda tyto hodnoty jsou shodné atomy a v tom případě nabývá hodnotu T. Pokud hodnotami argumentů jsou různé atomy, je výsledkem nil. Příklady: (setq L '(A B)) (A B) (atom L) nil (atom (car L)) T (atom (cdr L)) nil (eq 'A (car L)) T (eq 'A 'B) nil Speciální funkce cond (cond (test1... výsledek1) (test2... výsledek2)... (testn... výsledekn)) Tato funkce zajišťuje větvení výpočtu, tedy výběr z několika alternativ v závislosti na splnění zadaných podmínek. Každá alternativa je vyjádřena seznamem forem, kterému říkáme klauzule. Speciální forma cond se vyhodnocuje tak, že se postupně vyhodnocují formy test na začátku klauzulí, až se nalezne první s hodnotou testu různou od nil. Pro tuto klauzuli se vyhodnotí i ostatní výrazy v ní obsažené a hodnota posledního z nich je výsledkem celé formy cond. Pokud se při vyhodnocení nenalezla žádná úspěšná klauzule, je výsledná hodnota nil. Speciálním případem klauzule je seznam obsahující pouze jediný výraz, který pak slouží současně jako podmínka i jako výsledek. Příklad: forma pro získání absolutní hodnoty proměnné X: (cond ((< X 0) (- 0 X)) (T X)) - 15 -

2.4 Aritmetické a relační operace Aritmetické operace: +,, *, / Relační operace: =, <, >, <=, >=, /= (nerovno) Výrazy s těmito operacemi je třeba zapisovat v prefixovém tvaru. Příklady: infixová notace Lisp a + b + c (+ a b c) a * b c (- (* a b) c) a + 2 / (3 b) (+ a (/ 2 (- 3 b))) (a + 2) / (3 b) (/ (+ a 2) (- 3 b)) a < b c (< a (- b c)) x = 0 (= 0 x) x > 0 (> x 0) K porovnání s nulou je možno použít predikáty zerop, plusp a minusp (viz str. 22) 2.5 Definice funkcí Speciální funkce defun (defun jméno-funkce (par1 par2... parn) výraz1 výraz2... výrazm ) Speciální funkce defun zajišťuje definování nových funkcí. Její hodnotou je jméno definované funkce. Při aplikaci takto definované funkce se nejprve vyhodnotí argumenty a jejich hodnoty se navážou na proměnné par1, par2,..., parn. Potom se postupně vyhodnocují výrazy uvedené v těle funkce a hodnota posledního z nich je výsledkem aplikace. Po skončení aktivace funkce se zruší lokálně platné vazby argumentů a obnoví se platnost vazeb z nadřazeného prostředí. Příklad: Definice funkce pro test, zda zadaný seznam je prázdný (defun Prazdny (S) (cond ((atom S) (eq S nil)) (T nil))) - 16 -

Volné a vázané proměnné Výrazy v těle definované funkce nemusejí obsahovat jen proměnné reprezentující argumenty funkce, tj. vázané proměnné (tyto proměnné odpovídají v terminologii imperativního programování formálním parametrům funkce). Kromě vázaných proměnných se v těle funkce mohou vyskytovat i tzv. volné proměnné, které jsou vedle argumentů dalším nástrojem komunikace funkce s jejím okolím (tyto proměnné představují v terminologii imperativního programování nelokální proměnné). Rozlišení proměnných na volné a vázané se v Lispu netýká jen definice funkcí pomocí defun, ale také všech dalších lispovských konstrukcí, které zavádějí proměnné s lokálně vymezeným rozsahem platnosti (např. speciální funkce let viz s. 27). Existují dva způsoby určení vazby volné proměnné: Statické určení: Funkce si při své aktivaci přináší vazbu volné proměnné z globálního prostředí. Dynamické určení: Vazba volné proměnné se zjišťuje až v místě volání podle nejblíže nadřazeného prostředí, které tuto volnou proměnnou váže. Pro Lisp je typické dynamické určení rozsahu, avšak některé varianty Lispu používají statické určení rozsahu, neboť to umožňuje efektivnější kompilaci funkcí. Je třeba poznamenat, že používání volných proměnných je v rozporu se snahami o srozumitelnou a modulární strukturu programů. Příklad: Program pro testování typu vazby volné proměnné: (setq S '(STATICKA)) (defun Pripoj (X) (cons X S)) (defun Test (S Y) (pripoj Y)) Zkoušku provedeme aplikací funkce Test: (Test 'DYNAMICKA 'VAZBA) ; S je volna promenna Jiná podoba tohoto programu využívající funkci let je uvedena na str. 27. Programování rekurzivních funkcí Typické možnosti pro testování triviálních alternativ v definici funkce a pro redukci argumentu v rekurzivním volání: Pro číselný argument testujeme rovnost nule nebo jedné, případně i zápornou hodnotu argumentu; do rekurzivní větve dáváme zmenšenou hodnotu argumentu (typicky o hodnotu jedna), obecně hodnotu bližší triviální alternativě. Pro seznam zpracovávaný jen v nejvyšší úrovni testujeme, zda seznam je prázdný a argument redukujeme pomocí cdr. - 17 -

Pro obecně strukturovaný S-výraz testujeme většinou jako zvláštní případy prázdný seznam a atom (v tomto pořadí) a argument redukujeme pomocí car i cdr. Příklady: Definice funkce pro výpočet faktoriálu (defun Faktorial (N) (cond ((= N 0) 1) Definice funkce pro spojení dvou seznamů (defun Spoj (X Y) (T (* N (Faktorial (- N 1)))) )) (cond ((Prazdny X) Y) (T (cons (car X) (spoj (cdr X) Y))) )) Definice funkce pro testování shodnosti S-výrazů (defun Stejne (X Y) Úlohy na procvičení (cond ((atom X) (cond ((atom Y) (eq X Y)) (T nil))) ((atom Y) nil) ((stejne (car X) (car Y)) (stejne (cdr X) (cdr Y))) (T nil))) a) Definujte funkci pro výpočet absolutní hodnoty zadané proměnné (viz s. 82). b) Definujte funkci pro určení maxima ze dvou čísel (viz s. 82). c) S využitím funkce prázdný definujte funkci pro výpočet délky seznamu, tj. počtu prvků na nejvyšší úrovni (viz s. 82). d) S využitím funkce prázdný definujte funkci pro získání posledního prvku na nejvyšší úrovni seznamu (viz s. 82). e) S využitím funkce prázdný definujte funkci pro odstranění posledního prvku na nejvyšší úrovni seznamu (viz s. 82). - 18 -

2.6 Funkce pro zpracování seznamů Funkce append (append seznam-forma1... seznam-forman) Funkce append může mít proměnný počet argumentů. Hodnotami argumentů musejí být seznamy a výsledkem je seznam vzniklý spojením těchto seznamů. Při provedení funkce append se tolikrát provede primitivní konstruktor cons, kolik činí součet délek seznamů ve všech argumentech kromě posledního. Funkce list (append '(A B) '(C D)) (A B C D) (list forma1... forman) Funkce list může mít rovněž proměnný počet argumentů, ale jejich hodnotami mohou být libovolné S-výrazy. Výsledkem je seznam těchto S-výrazů. Jedno provedení funkce list má za následek tolik aplikací primitivního konstruktoru cons, kolik má list argumentů. (list '(A B) '(C D)) ((A B) (C D)) Příklad: Aplikace funkcí append a list pro obrácení pořadí prvků seznamu na nejvyšší úrovni (funkce prazdny byla definována na str. 16). Funkce last (defun obraceni (x) (cond ((prazdny x) nil) (t (append (obraceni (cdr x)) (last seznam-forma) (list (car x)) )))) Funkce last má jeden argument, jehož hodnotou musí být seznam. Výsledkem je seznam obsahující pouze poslední prvek seznamu v argumentu. (last '(A B C)) (C) (last '((A B) (C D))) ((C D)) Funkce length (length seznam-forma) Funkce length má jeden argument, jehož hodnotou musí být seznam. Výsledkem je počet prvků na nejvyšší úrovni seznamu v argumentu. (length '(A B C)) 3 (length '((A B) (C D))) 2-19 -

Funkce reverse (reverse seznam-forma) Funkce reverse má jeden argument, jehož hodnotou musí být seznam. Výsledkem je obrácený seznam, přičemž obracení se týká pouze nejvyšší úrovně seznamu a nižší vrstvy zůstávají nedotčeny. (reverse '(A B C)) (C B A) (reverse '((A B) (C D))) ((C D) (A B)) 2.7 Predikátové funkce Funkce equal (equal forma1 forma2) Funkce equal má dva argumenty, jejichž hodnotami mohou být libovolné S-výrazy. Pokud jsou shodné, výsledkem je T, v opačném případě je výsledkem nil. Funkce null (setq X '(A B C)) (A B C) (equal X X)) T (equal X '(A B C))) T (equal X 'X)) (equal 'X 'X)) (null seznam-forma) nil T Funkce null má jeden argument, jehož hodnotou by měl být seznam. Pokud je tento seznam prázdný, výsledkem je T, v opačném případě je výsledkem nil. (setq X '(A)) (A) (null X) (null '()) nil T (null (car X)) nil (null (cdr X)) T Funkce member (member forma seznam-forma) Funkce member má dva argumenty. Hodnotou prvého je libovolný S- výraz a hodnotou druhého musí být seznam. Funkce testuje existenci S-výrazu na nejvyšší úrovni seznamu. V případě úspěchu vrací nikoli hodnotu T, ale část seznamu začínající tímto S-výrazem. Při neúspěchu vrací samozřejmě nil. - 20 -

Funkce listp (setq X '(C A D (B D) A)) (C A D (B D) A) (member 'A X)) (A D (B D) A) (member 'B X)) nil (member '(B D) X)) ((B D) A) (listp forma) Funkce listp má jeden argument. Tato funkce testuje, zda hodnotou argumentu je seznam. (setq X '(A B C)) (A B C) (listp X) (listp (car X)) (listp (cdr X)) Funkce numberp (numberp forma) T nil T Funkce numberp má jeden argument a testuje, zda hodnotou tohoto argumentu je číslo. (setq X '(A 1 2)) (A 1 2) (numberp (car X)) nil (numberp (car (cdr X))) T Funkce symbolp (symbolp forma) Funkce symbolp má jeden argument a testuje, zda jeho hodnotou je symbol. (setq X '(A 1 2)) (A 1 2) (symbolp (car X)) T (symbolp (car (cdr X))) nil Funkce boundp (boundp forma) Funkce boundp má jeden argument, jehož hodnotou by měl být symbolický atom. Pokud je na tento atom navázána hodnota, funkce vrací T, jinak vrací nil. (setq X '(A B C)) (A B C) (boundp X) (boundp (car X)) T nil - 21 -

Funkce pro porovnávání s nulou (zerop forma) Funkce zerop má jeden argument, jehož hodnotou by mělo být číslo. Tato funkce testuje, zda hodnota argumentu je rovna nule. (plusp forma) Funkce plusp má jeden argument, jehož hodnotou by mělo být číslo. Tato funkce testuje, zda hodnota argumentu je kladná. (minusp forma) Funkce minusp má jeden argument, jehož hodnotou by mělo být číslo. Tato funkce testuje, zda hodnota argumentu je záporná. Příklady: Funkce not (setq X -5 (zerop X) Y 7) 7 nil (zerop (+ X 5)) T (plusp X) nil (plusp (+ X Y)) T (minusp X) T (minusp (+ X Y)) nil (not forma) Tato funkce je v podstatě synonymem pro predikát null. Je však doporučeno ji používat jako zdůraznění, že se nejedná o testování konce seznamu, ale o logickou negaci (v lispovském pojetí). Platí: Příklady: (not nil) => T (not x) => nil pro x nil (setq X '(A B C)) (A B C) (not (member 'B X)) nil (not (member 'D X)) T Funkce and a or Predikáty and a or jsou speciální funkce zajišťující výpočet logického součinu a logického součtu svých argumentů v lispovské interpretaci. - 22 -

(and forma1 forman) Funkce and vyhodnocuje své argumenty tak dlouho, až narazí na prvý s hodnotou ni1 a pak vrátí nil. Jinak vyhodnotí všechny argumenty a vrací hodnotu posledního. (or forma1 forman) Funkce or vyhodnocuje své argumenty tak dlouho, až narazí na prvý s hodnotou různou od ni1 a jeho hodnotu pak vrátí jako výsledek. Jinak vyhodnotí všechny argumenty a vrátí nil. Z uvedeného popisu vyplývá, že tyto speciální funkce vyhodnotí vždy jen tolik argumentů, kolik je zapotřebí k určení výsledku. Příklady: (setq X '(A B C D)) (A B C D) (and (member 'B X) (member 'C X)) (C D) (and (member 'A X) (member 'F X)) nil (or (member 'B X) (member 'C X)) (B C D) (or (member 'E X) (member 'F X)) nil Úlohy na procvičení a) Pomocí základních funkcí a funkcí null a equal definujte funkci pro test, zda S-výraz X je prvkem seznamu S na nejvyšší úrovni (viz s. 82). 2.8 Vyhodnocovací funkce Funkce eval (eval forma) Funkce eval je základem vyhodnocovacího systému Lispu. Tato funkce má jediný argument a pokud je použita v explicitním volání, dojde ke dvojímu vyhodnocení tohoto argumentu: poprvé se vyhodnotí jako argument kterékoliv jiné funkce, získaná hodnota se pak předá funkci eval a ta ji pak vyhodnotí znovu. Hodnota argumentu funkce eval musí mít jeden z těchto tvarů: číselný atom eval vrátí totéž číslo symbolický atom eval vrátí hodnotu vázanou na toto jméno seznam tvaru (f a l a 2... a n ), kde f je jméno funkce a každé a j lze opět vyhodnotit Prostřednictvím funkce eval dostává programátor velmi mocný nástroj: může totiž dynamicky vytvořit určitou část programu a takto vytvořenou část bezprostředně jako program vyhodnotit. Příklad: (setq A 'B B 'C) C - 23 -

(eval 'A) (eval A) Funkce apply B C (setq X '(A B) Y '(1 2 3) Z '(X Y)) (X Y) (eval (cons 'append Z)) (A B 1 2 3) Při vyhodnocení zápisu funkce v Lispu se vyhodnotí její argumenty, z jejich hodnot se udělá seznam a ten se předá k aplikaci. Ta část vyhodnocovacího systému, která aplikaci funkce provádí, je v Lispu k dispozici prostřednictvím funkce apply. (apply funkce-forma seznam-forma) Funkce apply má dva argumenty. Hodnotou prvého argumentu musí být jméno funkce a hodnotou druhého musí být seznam hodnot jejích argumentů. Platí vztah (apply 'fce '(val 1 val 2 val n )) = (fce arg 1 arg 2 arg n ) kde val i je hodnota argumentu arg i. (apply 'cons '(A (B C))) (A B C) Funkce funcall (funcall funkce-forma forma1 forman) Tímto zápisem se zajistí aplikace funkce, která je hodnotou výrazu funkce-forma, na argumenty vypočtené jako hodnoty výrazů forma1,, forman. (funcall 'cons 'A '(B C)) (A B C) 2.9 Řídicí programové struktury Spolu s funkcí cond je v Lispu základním nástrojem pro řízení výpočtu rekurze (viz str. 17). Použití rekurze má ovšem jisté nevýhody. Při rekurzivním volání funkcí dochází ke větší spotřebě strojového času, než kolik by potřeboval odpovídající iterační algoritmus. Dále jsme v tomto případě při výpočtu i té nejjednodušší rekurzivně definované funkce omezeni maximální kapacitou zásobníku, pomocí něhož systém provádí rekurzivní volání. Iterativní verze mají při výpočtu konstantní paměťové nároky a podobné nebezpečí u nich tedy nehrozí. Pokud je to možné, je vhodné používat koncovou rekurzi (viz str. 6), protože řada překladačů Lispu dovede automaticky převést koncovou rekurzi na iteraci. Příklady použití koncové rekurze: Obrácení pořadí prvků seznamu (defun obraceni_kr (x) (obrat x nil)) - 24 -

(defun obrat (sez revs) Délka seznamu (cond ((null sez) revs) (t (obrat (cdr sez) (cons (car sez) revs))) )) (defun delka_kr (x) (delkr x 0)) (defun delkr (x pocet) (cond ((null x) pocet) (t (delkr (cdr x) (+ 1 pocet))) )) Z důvodů možných problémů s použitím rekurze je i jazyk Lisp vybaven prostředky dovolujícími explicitní používání iteračních konstrukcí, přestože neodpovídají klasickému funkcionálnímu stylu. Speciální funkce do (do ((var1 ini-forma1 opak-forma1) ; předpisy pro práci (var2 ini-forma2 opak-forma2)... (varn ini-forman opak-forman)) (test výraz1 výraz2... výrazk) forma1 forma2... formam ) ; s proměnnými cyklu ; testovací klauzule ; tělo cyklu Výrazy opak-forma mohou být vynechány, výrazy ini-forma mohou být vynechány pouze současně s odpovídajícími výrazy opak-forma. Vynechány mohou být také výrazy test a současně výraz1 až výrazk v testovací klauzuli nebo výrazy forma1 až formam v těle cyklu do. Vyhodnocení formy do: 1. Vyhodnotí se všechny výrazy ini-forma určující počáteční hodnoty proměnných cyklu. 2. Výsledné hodnoty se navážou na proměnné; kde hodnota nebyla uvedena, dosadí se nil. 3. Je-li testovací klauzule neprázdná, vyhodnotí se test; a) je-li úspěšný, vyhodnotí se postupně všechny výrazy v klauzuli, cyklus se ukončí a výsledkem je hodnota posledního výrazu, b) je-li neúspěšný, pokračuje se krokem 5. 4. Je-li testovací klauzule prázdná, cyklus končí s hodnotou nil. 5. Vyhodnotí se postupně všechnu formy těla cyklu. 6. Vyhodnotí se všechny části opak-forma. - 25 -

7. Hodnoty získané v kroku 6 se navážou na příslušné proměnné. 8. Pokračuje se krokem 3. Je důležité si uvědomit, že vyhodnocení výrazů předepisujících počáteční nebo změnové hodnoty proměnných cyklu se provádí dříve, než se jakákoliv z těchto hodnot naváže na příslušnou proměnnou, tedy vlastně jakoby paralelně. Chování cyklu tudíž vůbec nezávisí na pořadí, v jakém na začátku cyklu uvedeme jednotlivé proměnné. Příklad: Obrácení pořadí prvků seznamu pomocí iterace (defun obraceni_i (x) (do ((sez x (cdr sez)) (revs nil (cons (car sez) revs))) ((null sez) revs) )) Speciální funkce dolist a dotimes (dolist (var list-forma result-forma) forma1... forman) Při vyhodnocování se na proměnnou var postupně navazují jednotlivé prvky seznamu, který se dostane vyhodnocení výrazu list-forma, přičemž pro každou hodnotu se provede vyhodnocení všech forem těla cyklu. Výsledná hodnota je pak určena hodnotou formy result-forma. (dotimes (var pocet-forma result-forma) forma1... forman) Při vyhodnocování se proměnné var postupně přiřazují hodnoty od 0 do hodnoty počet-forma 1, přičemž pro každou hodnotu se provede vyhodnocení všech forem těla cyklu. Výsledná hodnota je pak určena hodnotou formy result-forma. Příklady: Sečtení prvků číselného seznamu pomocí dolist (defun sum-list (num-list) (setq s 0) (dolist (p num-list s) (setq s (+ s p)))) Výpočet faktoriálu pomocí dotimes (defun fakt (n) (setq m 1) (dotimes (i n m) (setq i (+ i 1)) (setq m (* m i)))) - 26 -

Speciální funkce typu prog (prog1 výraz1 výraz2... výrazn) (prog2 výraz1 výraz2... výrazn) (progn výraz1 výraz2... výrazn) (progv (var1 var2... varn) (exp1 exp2... expn) forma1 forma2... formam) Funkce prog1, prog2 a progn vyhodnotí postupně své argumenty; prog1 vrací hodnotu prvního výrazu, prog2 vrací hodnotu druhého a progn vrací hodnotu posledního. Všechny tyto funkce tedy vytvářejí obdobu složeného příkazu jazyka Pascal. Funkce progv nejprve vyhodnotí všechny výrazy exp1 až expn, pak naváže jejich hodnoty na odpovídající proměnné, a potom postupně vyhodnocuje výrazy forma1 až formam. Výsledkem je hodnota poslední formy. Kromě uvedených funkcí existuje také speciální funkce prog, jejíž tělo je obdobou imperativního programu. V těle této funkce se dokonce může objevit funkce go, umožňující nepodmíněný skok. Speciální funkce let (let ((var1 ini-forma1) (var2 ini-forma2)... (varn ini-forman)) forma1 forma2... formam) Var je symbolický atom označující zaváděnou lokální proměnnou a ini-forma je (počáteční) hodnota této proměnné. Je také možné inicializační výraz vůbec neuvést a příslušná proměnná má pak počáteční hodnotu nil. Nejprve se vyhodnotí všechny výrazy ini-forma a teprve pak se provede jejich přiřazení proměnným (na pořadí proměnných tedy nezáleží). Potom se postupně vyhodnotí forma1 až formam, přičemž výsledkem je hodnota poslední formy. Příklad: Program pro test typu vazby volné proměnné (viz str. 17) s použitím funkce let: (setq S '(STATICKA)) (defun Pripoj (X) (defun Test (Y) (cons X S)) (pripoj Y)) (let ((S 'DYNAMICKA)) (Test 'VAZBA)) ; S je volna promenna - 27 -

Úlohy na procvičení a) Definujte funkci pro výpočet faktoriálu pomocí koncové rekurze (viz s. 83). b) Definujte funkci pro výpočet faktoriálu pomocí iterace (viz s. 83). c) Definujte funkci pro sečtení prvků číselného seznamu pomocí iterace (viz s. 83). 2.10 Mapovací funkcionály Často je zapotřebí aplikovat nějakou funkci na všechny prvky seznamu. Pro takovéto účely je výhodné používat mapovací funkcionály. Funkcionál mapcar (mapcar funkce-forma seznam-forma1... seznam-forman) Funkcionál mapcar aplikuje funkci, která je hodnotou výrazu funkce-forma na jednotlivé (stejnolehlé) prvky seznamů (tyto seznamy jsou hodnotami forem seznam-forma) a vrací seznam výsledků. (mapcar '+ '(1 2 3) '(3 2 1)) (4 4 4) (mapcar (car '(cons list)) '(A B C) '(D E F)) ((A.D) (B.E) (C.F)) (mapcar (cadr '(cons list)) '(A B C) '(D E F)) Pozn.: (cadr x) (car (cdr x)) ((A D) (B E) (C F)) Příklad: Určení počtu atomů na všech úrovních seznamu pomocí apply a mapcar. (defun PocetAtomu (S) Funkcionál mapc (cond ((null S) 0) ((atom S) 1) (T (apply '+ (mapcar 'PocetAtomu S))) )) (mapc funkce-forma seznam-forma1... seznam-forman) Funkcionál mapc aplikuje funkci na jednotlivé (stejnolehlé) prvky seznamů a vrací triviální hodnotu (zpravidla nil nebo první seznam). Funkcionál maplist (maplist funkce-forma seznam-forma1... seznam-forman) Funkcionál maplist aplikuje funkci na celé seznamy, pak na jejich zbytky atd., a vrací seznam výsledků. - 28 -

(maplist 'cons '(A B C) '(D E F)) (((A B C).(D E F)) ((B C).(E F)) (C.F)) (maplist 'append '(A B C) '(D E F)) Funkcionál mapl ((A B C D E F) (B C E F) (C F)) (mapl funkce-forma seznam-forma1... seznam-forman) Funkcionál mapl aplikuje funkci na celé seznamy, pak na jejich zbytky atd., a vrací triviální hodnotu (zpravidla nil nebo první seznam). Lambda výraz Ve spojení s mapovacími funkcionály se často používají anonymní funkce zavedené pomocí lambda výrazů. (lambda (par1 par2... parn) výraz1 výraz2... výrazm) Lambda-výraz představuje nepojmenovanou funkci. Seznam parametrů (lambda-seznam) určuje, jaké jsou vázané proměnné této funkce. Na ně se při aplikaci navážou hodnoty argumentů a pak se postupně vyhodnotí výrazy výraz1, výraz2,..., výrazm. Výsledná hodnota je určena hodnotou posledního výrazu. Lambda-výraz využíváme buď pro zavedení anonymní funkce v místě jejího použití nebo jako prostředek lokálního přizpůsobení počtu argumentů nějaké funkce (a fixaci zbývajících). Příklad: Přičtení skaláru ke každému prvku vektoru pomocí lambda a mapcar. (defun PrictiSkalar (Skalar Vektor) (mapcar '(lambda (X) (+ Skalar X)) Vektor)) Poznamenejme, že v některých implementacích Lispu je nutno před lambda výraz psát místo apostrofu dvojici znaků #'. (mapcar #'(lambda (X) (+ Skalar X)) Vektor)) Tato dvojice znaků má stejný účel jako samotný apostrof, tj. zabraňuje vyhodnocení následujícího S-výrazu, a používá se tehdy, jestliže následující S-výraz představuje funkci. 2.11 Definice a vyhodnocení maker Předpokládejme, že často potřebujeme navázat na nějakou proměnnou hodnotu nil a tudíž pro tento účel definujeme funkci: (defun Niluj (Arg) (setq Arg nil)) - 29 -

Pokusme se nyní tuto funkci aplikovat: (Niluj X) Chyba, nenavazana promenna Při aplikaci funkce Niluj se nejprve totiž vyhodnotí její argument. Protože na X dosud nebyla navázána žádná hodnota, je signalizována chyba. Zkusme nyní ověřit, co by se dělo, kdyby na X nějaká hodnota už navázána byla: (setq X 99) 99 (Niluj X) nil X 99 Vidíme, že ani teď to nefunguje. Hodnota nil se totiž v rámci funkce Niluj naváže na lokální proměnnou Arg, kdežto proměnná X zůstane beze změny. Pro řešení takovýchto situací v Lispu existují makra, která na rozdíl od funkcí poskytují jiný mechanismus vyhodnocení své aplikace. Speciální funkce defmacro (defmacro jméno-makra (par1 par2... parn) výraz1 výraz2... výrazm) Při vyhodnocení aplikace makra se na parametry makra navážou nevyhodnocené argumenty a pro ně se provede vyhodnocení jednotlivých výrazů v rámci prostředí, v němž se nachází definice makra. Tato první etapa vyhodnocení se nazývá expanze makra a jejím výsledkem je hodnota posledního výrazu z těla definice makra. Výsledek expanze se pak opět považuje za E-výraz a ten se ve druhé etapě znovu vyhodnotí, tentokrát ale v prostředí místa aplikace makra. Definice a použití operace Niluj jako makra vypadá následovně: (defmacro Niluj (Arg) (Niluj X) X nil (list 'setq Arg nil)) Niluj expanze (setq X nil) nil Jiným příkladem aplikace makra jsou aplikace zásobníkových operací. Předpokládejme, že zásobník je implementován jako seznam, přičemž začátek seznamu představuje vrchol zásobníku. V následujícím příkladě je definována operace vložení prvku na vrchol zásobníku. Příklad: (defmacro Push (Element Stack) (list 'setq Stack (list 'cons Element Stack))) - 30 -

(Push X Z) expanze (setq Z (cons X Z)) Aby se zkrátila a zjednodušila definice makra, byl zaveden mechanismus obráceného apostrofu, který nahrazuje použití funkce list. Obrácený apostrof funguje jako normální apostrof (tj. zabraňuje vyhodnocování) s tím, jakákoli čárka, která se vyskytne uvnitř jeho rozsahu, zruší jeho účinek (tj. povolí vyhodnocení) pro následující S-výraz. Výše definovaná makra můžeme pomocí této notace zapsat takto: (defmacro Niluj (Arg) (setq,arg nil)) (defmacro Push (Element Stack) (setq,stack (cons,element,stack))) 2.12 Modifikace struktur Ve striktně chápaném funkcionálním programování se datové struktury pouze vytvářejí a jako celek pak mohou být zařazovány do vyšších struktur nebo být zrušeny, nelze je však měnit. Každá modifikace struktury znamená v takovém případě její kompletní rekonstrukci (tj. nové vytvoření), i když se bude lišit např. jen v záměně jednoho atomu za jiný. Tento přístup by v praktických aplikacích mohl vést k neúnosným časovým nárokům nebo ke zbytečnému čerpání buněk volné paměti (což se v konečném efektu projeví mimo jiné opět zpomalením výpočtu). Jazyk Lisp proto dává uživateli možnost modifikovat existující datové struktury. Funkce rplaca a rplacd Názvy těchto pseudofunkcí jsou odvozeny z anglických výrazů replace car a replace cdr, které vyjadřují jejich sémantiku. (rplaca forma-tečka-dvojice forma-nový-car) Tato funkce modifikuje 1. část tečky-dvojice, která je hodnotou prvého argumentu tak, že do ní uloží odkaz na hodnotu druhého argumentu. Dojde tedy k náhradě prvního prvku tečky-dvojice za jiný, ale nestane se tak vytvořením nové tečky-dvojice, nýbrž modifikací původní struktury. (rplacd forma-tečka-dvojice forma-nový-cdr) Tato funkce modifikuje 2. část tečky-dvojice, která je hodnotou prvého argumentu tak, že do ní uloží odkaz na hodnotu druhého argumentu. Příklady: (setq X '(A B C)) (A B C) (setq Y '(1)) (1) (rplaca X Y) ((1) B C) X ((1) B C) (rplacd Y (cdr X)) (1 B C) - 31 -

Funkce nconc Y (1 B C) X ((1 B C) B C) (nconc seznam-forma1 seznam-forman) Funkce nconc může mít proměnný počet argumentů. Hodnotami argumentů musejí být seznamy a výsledkem je seznam vzniklý jejich spojením. Přitom se změní struktura všech těchto seznamů kromě posledního, což se projeví také ve všech strukturách, do nichž byly příslušné seznamy dříve začleněny. Příklady: Funkce setf (setq X '(A B)) (A B) (setq Y '(C D)) (C D) (setq Z '(E F)) (E F) (setq P (cons X Y)) ((A B) C D) (nconc X Y Z) (A B C D E F) X (A B C D E F) Y (C D E F) Z (E F) P ((A B C D E F) C D E F) (setf forma1 forma2) Funkce setf má dva argumenty. První musí určovat nějaké místo a druhý určuje hodnotu, která se na toto místo dosadí. Následující zápisy jsou tedy sémanticky ekvivalentní: Příklady: (rplaca X Y) (setf (car X) Y) (rplacd X Y) (setf (cdr X) Y) (setq X cons 'A 'B)) (A.B) X (setf (car X) 'C)) X (setf (cdr X) 'D)) X (A.B) C (C.B) D (C.D) - 32 -

Funkcionály mapcan a mapcon (mapcan funkce-forma seznam-forma1 seznam-forman) Funguje jako funkcionál mapcar, ale vrací seznam vzniklý spojením dílčích výsledků pomocí funkce nconc. (mapcon funkce-forma seznam-forma1 seznam-forman) Funguje jako maplist, ale vrací seznam vzniklý spojením dílčích výsledků pomocí funkce nconc. 2.13 Reprezentace atomů Při grafickém znázornění paměťové reprezentace symbolických výrazů Lispu je odkaz na atom vyjadřován šipkou směřující ke jménu daného atomu. Zdálo by se, že k paměťové reprezentaci symbolického atomu bude stačit uložení příslušné posloupnosti znaků. Jméno je však pouze jednou z tzv. vlastností atomu a každý symbolický atom může mít teoreticky libovolný počet vlastností. Některé z vlastností atomu vyhodnocovací systém Lispu vytváří a využívá automaticky při zpracování programu a takové vlastnosti označujeme jako standardní. Vedle standardních vlastností může uživatel přiřadit atomu libovolné další vlastnosti a využívat je podle vlastní úvahy. To spolu s jednoznačnou reprezentací symbolických atomů v Lispu poskytuje zcela nové programovací možnosti, které tvoří základ pro daty řízené programování a objektově orientované programování. Seznam vlastností atomu K reprezentaci symbolického atomu slouží seznam vlastností atomu (property-list, zkráceně P- seznam), který má následující strukturu: (ind1 hodn1 ind2 hodn2 indn hodnn) kde ind označuje indikátor vlastnosti a hodn její hodnotu. Reprezentace atomu v paměti je tedy obdobná jako reprezentace neatomických S-výrazů a je založena na využití cons-buněk. P- seznam nelze ovšem chápat jako obyčejný seznam, neboť je to vnitřní reprezentace atomu, a pro práci s ním je tedy nutno používat speciální funkce. Struktura reprezentující symbolický atom se vytvoří při prvním výskytu daného atomu v programu nebo v datech, a od té chvíle se každý další výskyt vyjádří odkazem na tuto jedinou reprezentaci. Přestože všechny výskyty daného atomu reprezentuje jediná paměťová struktura, neznamená to, že tato struktura zůstává během výpočtu neměnná. Pro číselné atomy jedinečnost reprezentace neplatí. Protože číselný atom představuje pouze příslušnou číselnou hodnotu, týká se možnost existence různých vlastností pouze symbolických atomů. Příklady standardních vlastností atomů: PNAME hodnotou je řetězec znaků tvořící jméno atomu (print-name). APVAL slouží pro uložení hodnoty navázané na proměnnou - 33 -

EXPR slouží k uložení uživatelské definice funkce; její hodnotou je příslušný lambdavýraz. SUBR slouží k uložení odkazu na funkci ve strojovém kódu. FEXPR charakterizuje atom jako speciální funkci a za hodnotu má příslušnou lambdadefinici. FSUBR charakterizuje atom jako standardní speciální funkci a za hodnotu má odkaz na její realizaci ve strojovém kódu. Tentýž atom může být vázanou proměnnou i jménem funkce a kontext jeho použití rozhoduje, kterou interpretaci systém Lisp zvolí. Vlastnosti EXPR a FEXPR (a ovšem též SUBR a FSUBR) se u jednoho atomu nemohou vyskytovat současně. Funkce pro práci se seznamy vlastností (get atom-forma indikátor-forma) Funkce get má dva argumenty. Hodnotou prvého by měl být atom a hodnotou druhého indikátor vlastnosti (také atom). Tato funkce se používá pro zjištění existence a hodnoty vlastnosti s určeným indikátorem v P-seznamu daného atomu. Pokud tento atom požadovanou vlastnost nemá, je výsledkem nil. (putprop atom-forma hodnota-forma indikátor-forma) Funkce putprop zajistí uložení hodnoty vlastnosti určené indikátorem vlastnosti do P-seznamu daného atomu. Tato funkce vrací ukládanou hodnotu. (remprop atom-forma indikátor-forma) Funkce remprop zajistí vypuštění vlastnosti určené indikátorem (včetně její hodnoty) z P- seznamu daného atomu. Tato funkce vrací nil. Příklady: (putprop 'A '(B C) 'SOUSEDE) (B C) (get 'A 'SOUSEDE) (B C) V jazyku Common Lisp funkce putprop chybí. Vložení hodnoty do seznamu vlastností se zajišťuje pomocí kombinace funkcí setf a get: (setf (get atom-forma indikátor-forma) hodnota-forma)hodnotu vlastnosti můžeme do P-seznamu vložit také pomocí kombinace funkcí setf a get: (setf (get 'B 'SOUSEDE) '(A C D)) (A C D) (get 'B 'SOUSEDE) (A C D) Pomocí seznamů vlastností můžeme implementovat např. grafovou strukturu. Vrcholy (uzly) grafu mohou být reprezentovány atomy a hrany vlastnostmi sousedé (v případě neorientovaných hran) nebo následníci (v případě orientovaných hran). Jiným příkladem použití seznamů vlastností je implementace rámců (frames), které jsou jedním z prostředků reprezentace znalostí v expertních systémech. - 34 -

Funkce gensym (gensym) Hodnotou aplikace funkce gensym bez argumentů je vygenerovaný atom. Jeho jméno je tvořeno aktuálně platným prefixem (implicitně G) za nímž následuje aktuální pořadové číslo (na počátku obvykle 0). Po každém volání funkce gensym se pořadové číslo zvětšuje, takže je zaručena různost generovaných atomů. (gensym výraz) Hodnota argumentu určuje jméno generovaného atomu takto: je-li hodnotou symbolický atom (v Common Lispu to musí být řetězec), použije se jako prefix generovaného atomu, je-li hodnotou nezáporné celé číslo, použije se jako pořadové číslo generovaného atomu. 2.14 Vstup a výstup Vstup/výstup z/do standardních zařízení (read) Funkce read přečte ucelený S-výraz a ten vrátí jako výslednou hodnotu. (print výraz) (princ výraz) Tyto funkce zajistí vypsání hodnotu argumentu (zobrazený S-výraz je současně výslednou hodnotou těchto funkcí). Funkce print pak přejde na nový řádek. Funkce print zobrazí hodnotu argumentu ve tvaru, který je zpětně čitelný pomocí funkce read, kdežto funkce princ potlačuje některé speciální znaky. (terpri) Funkce terpri ukončí aktuální řádek a vrátí nil. Příklad: (setq x (read)) (print x) Vstup/výstup z/do souboru (open jméno-souboru) Funkce open zajistí otevření souboru. Jméno souboru je řetězec. Funkce vrací ukazatel na soubor. (close ukazatel-na-soubor) Funkce close zajistí uzavření souboru. Příklad: (setq f (open "C:/adresar/data.txt")) (close f) - 35 -

Výše uvedené funkce read, print, princ, terpri mohou být používány i pro soubory: (read ukazatel-na-soubor) (print výraz ukazatel-na-soubor) (princ výraz ukazatel-na-soubor) (terpri soubor) Aplikace funkce read může mít i takovouto podobu: (read ukazatel-na-soubor eof-error eof-value) Je-li argument eof-error různý od nil, je při dosažení konce souboru (eof) signalizována chyba. Má-li tento argument hodnotu nil, pak je při dosažení konce souboru vrácena hodnota určená argumentem eof-value. Příklad: Výpočet součtu čísel ze zadaného souboru. ; Konec souboru je signalizovan symbolem f (defun secti1 (f) (setq s 0) (do ((x (read f nil f) (read f nil f))) ((eq x f) s) (setq s (+ s x)))) ; Konec souboru je signalizovan symbolem nil (defun secti2 (f) (setq s 0) (do ((x (read f nil nil) (read f nil nil))) Vstup/výstup znaků (read-char) ((null x) s) (setq s (+ s x)))) (read-char ukazatel-na-soubor) (read-char ukazatel-na-soubor eof-error eof-value) Funkce read-char přečte jeden znak a jako výslednou hodnotu vrátí #\znak. (write-char výraz) (write-char výraz ukazatel-na-soubor) Hodnotou výrazu by měl být znak reprezentovaný trojicí #\znak. Funkce write-char zajistí vypsání tohoto znaku (trojice #\znak je současně výslednou hodnotou této funkce). - 36 -