2. Jazyk Lisp - Ústav automatizace a informatiky

Transkript

2. Jazyk Lisp - Ústav automatizace a informatiky
VYSOKÉ UČENÍ TECHNICKÉ V BRNĚ
FAKULTA STROJNÍHO INŽENÝRSTVÍ
Ústav automatizace a informatiky
Jazyky pro umělou inteligenci
Jiří Dvořák
2005
Jiří Dvořák
Jazyky pro umělou inteligenci
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-
Jiří Dvořák
Jazyky pro umělou inteligenci
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-
Jiří Dvořák
Jazyky pro umělou inteligenci
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-
Jiří Dvořák
Jazyky pro umělou inteligenci
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
Vyhodnocení
Prostředí
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-
Jiří Dvořák
•
FP
•
Hope
•
Miranda [21]
•
Haskell
Jazyky pro umělou inteligenci
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
( x = 0)
y +1

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 ≤ 1)
n
fib( n ) = 
 fib( n − 2) + 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
( n = 0)
1
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 = y)
x

nsd ( x, y ) = nsd ( x − y, y ) ( x > y )
nsd ( x, y − x) ( jinak )

Příklady převodu definice faktoriálu na koncovou rekurzi:
-6-
Jiří Dvořák
a)
Jazyky pro umělou inteligenci
fakt ( n ) = fakt1(1, n )
(i = 0)
m
fakt1( m, i ) = 
 fakt1( m * i, i − 1) (i > 0)
b)
fakt ( n ) = fakt 2(1, n, 1)
(i > n)
m
fakt 2(m, n, i ) = 
 fakt 2(m * i, n, i + 1) (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-
Jiří Dvořák
Jazyky pro umělou inteligenci
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
•
ECLiPSe
•
Gödel
•
Mercury
-8-
Jiří Dvořák
Jazyky pro umělou inteligenci
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)
čísla
celá
reálná
symboly
strukturované
tečka-dvojice
speciální znaky identifikátory
Obr. 2.1. Typy dat v Lispu
-9-
seznamy
Jiří Dvořák
Jazyky pro umělou inteligenci
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:
•
speciální znaky: + - * < > =
•
identifikátory.
123 -5.46
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
((A.B).C)
B
C
A
B
Obr. 2.3. Příklady reprezentace tečka-dvojic
- 10 -
Jiří Dvořák
Jazyky pro umělou inteligenci
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
Obr. 2.5. Příklady reprezentace seznamů
- 11 -
C
Jiří Dvořák
Jazyky pro umělou inteligenci
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 -
Jiří Dvořák
Jazyky pro umělou inteligenci
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
⇒ 0
X
(setq Y '(a b c)) ⇒ (a b c)
⇒ (a b c)
Y
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.
(setq prom1 forma1
...
promN formaN)
Příklad:
(setq A 'B
B 'C) ⇒ C
A
⇒ B
B
⇒ C
Funkce car a cdr
(car forma)
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 -
Jiří Dvořák
Jazyky pro umělou inteligenci
(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))
⇒ A
(cdr '(A))
⇒ nil
(car '(A.B))
⇒ A
(cdr '(A.B))
⇒ B
(car '(A B))
⇒ A
(cdr '(A B))
⇒ (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:
(caar X) ≡ (car (car X))
(cadr X) ≡ (car (cdr X))
Funkce cons
(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 -
Jiří Dvořák
Jazyky pro umělou inteligenci
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 -
Jiří Dvořák
Jazyky pro umělou inteligenci
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 -
Jiří Dvořák
Jazyky pro umělou inteligenci
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))
; S je volna
promenna
(defun Test (S Y)
(pripoj Y))
Zkoušku provedeme aplikací funkce Test:
(Test 'DYNAMICKA 'VAZBA)
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 -
Jiří Dvořák
•
Jazyky pro umělou inteligenci
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)
(T (* N (Faktorial (- N 1)))) ))
•
Definice funkce pro spojení dvou seznamů
(defun Spoj (X Y)
(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)
(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)))
Úlohy na procvičení
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 -
Jiří Dvořák
Jazyky pro umělou inteligenci
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.
(append '(A B) '(C D)) ⇒ (A B C D)
Funkce list
(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).
(defun obraceni (x)
(cond ((prazdny x) nil)
(t (append (obraceni (cdr x))
(list (car x)) ))))
Funkce last
(last seznam-forma)
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 -
Jiří Dvořák
Jazyky pro umělou inteligenci
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.
⇒ (C B A)
(reverse '(A B C))
(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.
(setq X '(A B C))
⇒ (A B C)
(equal X X))
⇒ T
(equal X '(A B C))) ⇒ T
(equal X 'X))
⇒ nil
(equal 'X 'X))
⇒ T
Funkce null
(null seznam-forma)
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)
⇒ nil
(null '())
⇒ 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 -
Jiří Dvořák
Jazyky pro umělou inteligenci
(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)
Funkce listp
(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)
⇒ T
(listp (car X))
⇒ nil
(listp (cdr X))
⇒ T
Funkce numberp
(numberp forma)
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)
⇒ T
(boundp (car X))
⇒ nil
- 21 -
Jiří Dvořák
Jazyky pro umělou inteligenci
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:
(setq X -5
Y 7)
⇒ 7
⇒ nil
(zerop X)
(zerop (+ X 5)) ⇒ T
⇒ nil
(plusp X)
(plusp (+ X Y)) ⇒ T
(minusp X)
⇒ T
(minusp (+ X Y)) ⇒ nil
Funkce not
(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í:
(not nil) => T
(not x)
=> nil pro x ≠ nil
Příklady:
(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 -
Jiří Dvořák
Jazyky pro umělou inteligenci
(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 al a2 ... an), kde f je jméno funkce a každé aj 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 -
Jiří Dvořák
Jazyky pro umělou inteligenci
(eval 'A)
⇒ B
(eval A)
⇒ C
(setq X '(A B)
Y '(1 2 3)
Z '(X Y)) ⇒ (X Y)
(eval (cons 'append Z))
⇒ (A B 1 2 3)
Funkce apply
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 '(val1 val2 … valn)) = (fce arg1 arg2 … argn)
kde vali je hodnota argumentu argi.
(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 -
Jiří Dvořák
Jazyky pro umělou inteligenci
(defun obrat (sez revs)
(cond ((null sez) revs)
(t (obrat (cdr sez) (cons (car sez) revs))) ))
•
Délka seznamu
(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)
; s proměnnými cyklu
...
(varN ini-formaN opak-formaN))
(test výraz1 výraz2 ... výrazK)
; testovací klauzule
forma1
; tělo cyklu
forma2
...
formaM )
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 -
Jiří Dvořák
Jazyky pro umělou inteligenci
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 -
Jiří Dvořák
Jazyky pro umělou inteligenci
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)
(cons X S))
; S je volna
(defun Test (Y)
(pripoj Y))
(let ((S 'DYNAMICKA))
(Test 'VAZBA))
- 27 -
promenna
Jiří Dvořák
Jazyky pro umělou inteligenci
Ú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))
⇒ ((A D) (B E) (C F))
Pozn.: (cadr x) ≡ (car (cdr x))
Příklad: Určení počtu atomů na všech úrovních seznamu pomocí apply a mapcar.
(defun PocetAtomu (S)
(cond ((null S) 0)
((atom S) 1)
(T (apply '+ (mapcar 'PocetAtomu S))) ))
Funkcionál mapc
(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 -
Jiří Dvořák
Jazyky pro umělou inteligenci
(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))
⇒ ((A B C D E F) (B C E F) (C F))
Funkcionál mapl
(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 -
Jiří Dvořák
Jazyky pro umělou inteligenci
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
⇒ nil
(Niluj X)
⇒ 99
X
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)
(list 'setq Arg nil)) ⇒ Niluj
(Niluj X)
⇒
(setq X nil) ⇒ nil
expanze
X ⇒ 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 -
Jiří Dvořák
Jazyky pro umělou inteligenci
(Push X Z)
⇒
(setq Z (cons X Z))
expanze
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 -
Jiří Dvořák
Jazyky pro umělou inteligenci
Y
⇒ (1 B C)
X
⇒ ((1 B C) B C)
Funkce nconc
(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:
(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)
Funkce setf
(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í:
(rplaca X Y) ≡ (setf (car X) Y)
(rplacd X Y) ≡ (setf (cdr X) Y)
Příklady:
(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 -
Jiří Dvořák
Jazyky pro umělou inteligenci
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ě Pseznam), 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. Pseznam 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 -
Jiří Dvořák
Jazyky pro umělou inteligenci
•
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 Pseznamu 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 -
Jiří Dvořák
Jazyky pro umělou inteligenci
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 -
Jiří Dvořák
Jazyky pro umělou inteligenci
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)))
((null x) s)
(setq s (+ s x))))
Vstup/výstup znaků
(read-char)
(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 -
Jiří Dvořák
Jazyky pro umělou inteligenci
3. Využití jazyka Lisp pro řešení úloh umělé inteligence
3.1 Přehled aplikací
Kniha [4] je věnována programování v Lispu a jeho použití pro problémy umělé inteligence. Jsou
zde popsány následující programovací techniky UI:
•
diskriminační sítě
•
řídicí struktury agendy
•
deduktivní vyhledávání informací
•
produkční systémy
•
backtracking
V knize [13] je ukázáno použití Lispu pro řešení následujících:
•
strategie prohledávání
•
porovnávání se vzorem (pattern matching)
•
expertní systémy
•
sémantické sítě
•
strojové učení
Kniha [8] je věnována jazyku Scheme, který je jedním z dialektů Lispu. V kapitole věnované
umělé inteligenci jsou popsány tyto problémy:
•
prohledávání
•
plánování
•
expertní systémy
•
zpracování přirozeného jazyka
•
robotika
•
rozpoznávání objektů
V knize [2] je ukázáno použití Lispu pro řešení následujících úloh:
•
reprezentace znalostí
•
prohledávání
•
mechanismy inference
•
porovnávání se vzorem
•
zpracování přirozeného jazyka
V knize [24] jsou analyzovány následující problémy.
•
porovnávání se vzorem
- 37 -
Jiří Dvořák
Jazyky pro umělou inteligenci
•
zpracování přirozeného jazyka
•
pravidlové expertní systémy
•
implementace rámců
Kniha [22] uvádí jako příklady aplikací Lispu tyto úlohy:
•
porovnávání se vzorem
•
implementace asociativních databází
Významnou aplikací Lispu je tvorba expertních (znalostních) systémů. Touto problematikou se
zabývá např. kniha [23]. V jazyku Common Lisp byl vytvořen např. systém GBB (Generic
Blackboard Builder), což je objektově orientované programové prostředí pro vývoj systémů typu
tabule. Na Lispu je založený také prázdný expertní systém ART.
Další příklady aplikace Lispu je možno nalézt na stránkách následujících firem:
•
Franz Inc.
Tato firma nabízí Allegro CL, což je dynamické objektově orientované vývojové prostředí
pro Common Lisp, a jeho aplikace.
•
Xanalys LispWorks
Firma se zabývá vývojem softwaru založeného na jazyce Common Lisp.
3.2 Příklad využití Lispu pro tvorbu expertního systému
Následující příklad obsahuje jednoduchý inferenční mechanismus pro případ, že bázi znalostí
tvoří seznam pravidel tvaru
(rule identifikator
(if
podminka1 … podminkaN)
(then zaver1 … zaverM))
Podmínky i závěry mohou být atomy nebo seznamy. Například
(rule R1 (if zivocich_ma_peri) (then zivocich_je_ptak))
nebo
(rule R1 (if (zivocich ma peri)) (then (zivocich je ptak)))
Závěry pravidel mohou vystupovat jako podmínky v jiných pravidlech. Například
(rule R2 (if (zivocich je ptak) (zivocich neleta)
(zivocich plave) (zivocich je cerny a bily))
(then (zivocich je tucnak)))
Báze faktů je tvořena seznamem faktů, které mohou mít tyto tvary:
(ano podminka)
(ne podminka)
(ano zaver)
- 38 -
Jiří Dvořák
Jazyky pro umělou inteligenci
Fakta mohou být na počátku vložena do seznamu faktů, nebo mohou být v případě potřeby
zjišťována dotazem na uživatele. Lze se ovšem ptát pouze na takové věci, které nejsou
odvoditelné z jiných pravidel (tj., které se nevyskytují v závěrech jiných pravidel.
Příklad inferenčního mechanismu
Uvedený inferenční mechanismus vychází z programu, který je součástí souboru příkladů
příslušných k systému Xlisp a je rozšířen o možnost komunikace s uživatelem.
; Funkce testuje, zda se odpoved na danou podmínku nachazi
; v seznamu faktu
(defun memb (item list)
(cond((null list) nil)
((equal item (car (cdr (car list)))) list)
(t (memb item (cdr list)))))
; Funkce pridava zaver do seznamu zaveru, jestlize tam jeste neni
(defun remember (newconc)
(cond((member newconc conclusions) nil)
(t (setq conclusions (cons newconc conclusions)))))
; Funkce pridava fakt do seznamu faktu, jestlize tam jeste neni
(defun rememb (newfact)
(cond((memb (car (cdr newfact)) facts) nil)
(t ( setq facts (cons newfact facts)) newfact)))
;
;
;
;
;
Funkce zjistuje, zda se hodnota podminky (ano/ne) nachazi
v bazi faktu. Jestlize se tam nenachazi a nejedna se o zaver
jineho pravidla, zjistuje hodnotu dotazem na uzivatele. Funkce
vraci hodnotu T (hodnota ano) nebo nil (hodnota ne, nebo se
jedna o zaver jineho pravidla a hodnota neni znama).
(defun recall (afact)
(setq pom (memb afact facts))
(cond (pom (eq 'ano (car (car pom))))
((member afact conclusions) nil)
(t (terpri)
(print (list "Plati " afact "? (ano/ne)"))
(cond ((eq (read) 'ano)
(rememb (list 'ano afact)) t)
(t (rememb (list 'ne afact)) nil)))))
; Funkce testuje, zda jsou splneny vsechny podminky zadane
; v podminkove casti nejakeho pravidla
- 39 -
Jiří Dvořák
Jazyky pro umělou inteligenci
(defun testif (iflist)
(cond((null iflist) t)
((recall (car iflist)) (testif (cdr iflist)))
(t nil)))
; Funkce pridava zavery nejakeho pravidla do seznamu zaveru
(defun usethen (thenlist)
(cond ((null thenlist) nil)
(t (remember (car thenlist))
(usethen (cdr thenlist)))))
; Funkce prochazi seznam pravidel a pridava jejich zavery do
; seznamu zaveru
(defun searchrules (listrules)
(cond ((null listrules) nil)
(t (usethen (cdr(car(cdr(cdr(cdr (car listrules)))))))
(searchrules (cdr listrules)))))
; Funkce pridava zavery nejakeho pravidla do seznamu faktu ve
; tvaru (ano zaver) a vraci seznam pridanych zaveru
(defun useth (thenlist addlist)
(cond ((null thenlist) addlist)
((rememb (list 'ano (car thenlist)))
(useth (cdr thenlist)
(cons (car thenlist) addlist)))
(t (useth (cdr thenlist) addlist))))
;
;
;
;
Funkce zpracovava dane pravidlo. Pokud jsou splneny vsechny
podminky, pridava zavery do seznamu faktu (pokud tam jeste
nejsou). Skutecne pridane zavery jsou pak vypisovany na
obrazovku.
(defun tryrule (rule)
(setq ifrules (cdr(car(cdr(cdr rule)))))
(setq thenlist (cdr(car(cdr(cdr(cdr rule))))))
(setq addlist nil)
(cond (( testif ifrules)
(cond ((setq addlist (useth thenlist addlist))
(terpri)
(print (list "Rule " (car(cdr rule))
"Deduced " addlist)) t)
(t nil)))
(t nil)))
- 40 -
Jiří Dvořák
Jazyky pro umělou inteligenci
; Funkce postupne zpracovava jednotliva pravidla dokud neodvodi
; nejake nove zavery (pak vraci hodnotu T) nebo dokud nevycerpa
; seznam pravidel (pak vraci nil).
(defun stepforward (rulelist)
(cond((null rulelist) nil) ; all done
((tryrule (car rulelist)) t)
(t (stepforward(cdr rulelist)))))
;
;
;
;
Funkce prochazi seznam pravidel a zpracovava jednotliva
pravidla. Jestlize byly odvozeny nejake nove zavery, pokracuje
v prochazeni znovu od zacatku. Jestlize pri poslednim pruchodu
zadne nove zavery odvozeny nebyly, vraci hodnotu T.
(defun deduce ()
(cond((stepforward rules) (deduce))
(t t)))
; Funkce vytvori seznam zaveru pravidel a zahajuje proces reseni
(defun start ()
(searchrules rules)
(deduce))
; Inicializace
(setq conclusions nil)
(setq facts nil)
; zde je mozno vlozit seznam vychozich faktu do promenne facts
; (setq facts '(fact1 fact2 ... ))
; Zde je nutno vlozit seznam pravidel do promenne rules
; (setq rules '(rule1 rule2 ... ))
- 41 -
Jiří Dvořák
Jazyky pro umělou inteligenci
4. Prolog
Prolog znamená programování v logice a je vyústěním snahy o použití logiky jako
programovacího jazyka. Ve srovnání s ostatními programovacími jazyky přinesl Prolog tuto
změnu programovacího stylu: namísto otázky, JAK se má získat výsledek, se uživatel zajímá
o to, CO platí mezi objekty, s nimiž jeho program pracuje (tím se Prolog podobá specifikačním
jazykům). Prolog je tedy vhodným prostředkem pro řešení takových úloh, ve kterých zkoumáme
vztahy mezi objekty.
Podobně jako v Lispu můžeme za základ systému Prolog považovat jakýsi univerzální
vyhodnocovací systém (interpret). Namísto definic funkcí dostává tento systém definice relací
(vztahů), kterými dotváří své pracovní prostředí. Toto prostředí spolu se standardním
odvozovacím mechanismem pak systém používá při zodpovídání dotazů uživatele na platnost
nebo neplatnost konkrétních případů vztahů mezi objekty.
Historie jazyka Prolog:
•
A. Colmerauer a P. Roussel (Universita v Marseille, 1972): Prvá verze Prologu jako
prostředek pro efektivní odvozování důsledků z určité třídy formulí predikátové logiky.
•
R. Kowalski (1974): Teoretický model Prologu na základě procedurální implementace
Hornových klauzulí.
•
D. Warren (Edinburgh, 1974): Efektivní implementace na počítači DEC-10; zahájení éry
překladačů Prologu.
•
Japonský projekt počítačů 5.generace (1981): Prolog zvolen jako základní jazyk logického
procesoru centrální jednotky počítače; vyvolání vlny zájmu o Prolog a metody logického
programování.
Implementace jazyka Prolog:
•
Amzi! Logic Explorer
•
LPA Prolog
•
Quintus Prolog
•
SICStus Prolog 3
•
Strawberry Prolog
•
SWI-Prolog
•
Visual Prolog
V této kapitole jsou podány pouze základy jazyka Prolog. Podrobněji je možné se s tímto
jazykem seznámit v knihách [1], [3], [5], [6], [9], [17], [19], [20].
4.1 Charakteristika a příklad programu
Program v Prologu sestává z klauzulí. Existují tři typy klauzulí: fakta, pravidla a dotazy. Klauzule
definující stejnou relaci vytvářejí tzv. proceduru. Definice vztahů vyjádřené fakty a pravidly se
po zadání stávají součástí pracovního prostředí Prologu nazývaného databáze.
- 42 -
Jiří Dvořák
Jazyky pro umělou inteligenci
Pravidla vyjadřují tvrzení závislá na splnění nějakých podmínek. Každé pravidlo má hlavu a
(neprázdné) tělo, které je konjunkcí cílů.
Fakta se používají k vyjádření bezpodmínečně pravdivých tvrzení. Fakta jsou klauzule s hlavou,
ale s prázdným tělem.
Dotazy vyvolávají výpočet programu; jeho smyslem je zjistit, zda nějaké tvrzení platí či nikoliv.
Každý dotaz je klauzule bez hlavy.
Zodpovězení dotazu probíhá postupným ověřováním splnitelnosti jednotlivých cílů. Je-li
výsledek vyhodnocení cíle kladný, říkáme, že cíl byl úspěšný (uspěl) a že je splnitelný. V případě
záporného výsledku říkáme, že cíl byl neúspěšný (neuspěl, selhal) a že je nesplnitelný.
Během procesu vyhodnocení cíle (dotazu) dochází v případě úspěchu k nastavení proměnných na
takové hodnoty, které umožní jeho splnitelnost. Je-li více možností, jak dosáhnout splnění cíle,
Prolog je schopen je všechny zjistit pomocí postupu nazývaného zpětné prohledávání
(backtracking).
Neúspěch při vyhodnocení cíle neznamená, že Prolog dokázal jeho neplatnost, ale pouze to, že
nebyl schopen ze své databáze dokázat jeho platnost.
Často se pro úvod do Prologu požívá příklad rodinných vztahů. Uvažujme část rodokmenu nějaké
rodiny, uvedenou na obr. 4.1.
Vladislav
Ludvík
Johana
Anna
Ferdinand
Karel
Maxmilián
Marie
Rudolf
Matyáš
Obr. 4.1. Příklad rodinných vztahů
V Prologu můžeme tyto rodinné vztahy zapsat takto:
rodic(vladislav,ludvik).
rodic(vladislav,anna).
- 43 -
Jiří Dvořák
Jazyky pro umělou inteligenci
rodic(johana,ferdinand).
rodic(johana,karel).
rodic(anna,maxmilian).
rodic(ferdinand,maxmilian).
rodic(karel,marie).
rodic(maxmilian,rudolf).
rodic(maxmilian,matyas).
rodic(marie,rudolf).
rodic(marie,matyas).
Z hlediska syntaxe Prologu se jedná o fakta. Souhrn těchto faktů definuje vztah rodic. Zápis
rodic(x, y) zde interpretujeme tak, že x je rodičem y. Všechna jména píšeme s malým počátečním
písmenem, protože identifikátory, které začínají velkým písmenem, se v Prologu chápou jako
proměnné.
Uvedená fakta můžeme doplnit údaji o pohlaví:
muz(vladislav).
muz(ludvik).
muz(ferdinand).
muz(karel).
muz(maxmilian).
muz(rudolf).
muz(matyas).
zena(johana).
zena(anna).
zena(marie).
Nyní uveďme příklady dotazů, které můžeme našemu prologovskému programu klást (dotazy
začínají otazníkem, řádky bez otazníku představují odpovědi systému). Začněme jednoduchými
dotazy, které neobsahují proměnné:
?- rodic(vladislav,ludvik).
yes
?- rodic(karel,rudolf).
no
Zajímavější je, když se v dotazu vyskytují proměnné. Systém však v případě úspěšného
vyhodnocení dotazu (splnění cíle) neodpoví pouze yes, ale vypíše hodnoty proměnných, pro něž
je daný cíl splněn. Pokud stiskneme klávesu středník, systém se pokusí o další vyhodnocení. Je-li
další vyhodnocení neúspěšné, napíše no.
?- rodic(X,maxmilian).
X = anna;
X = ferdinand;
no
Je možné zadat dotaz, který obsahuje pouze proměnné:
?- rodic(X,Y).
- 44 -
Jiří Dvořák
Jazyky pro umělou inteligenci
X = vladislav, Y = ludvik;
X = vladislav, Y = anna;
X = johana, Y = ferdinand;
X = johana, Y = karel;
...
Po požadavku o opakované vyhodnocení se postupně vypisují hodnoty proměnných, pro které je
cíl splněn.
Můžeme pokládat i složené dotazy, představující konjunkci cílů (cíle v dotazu se oddělují
čárkami). Dotaz „Je Marie vnučkou Johany? “ se může zadat takto:
?- rodic(johana,X),rodic(X,marie).
X = karel;
no
Systém postupuje tak, že nejprve se na proměnnou X naváže hodnota ferdinand, neboť první fakt,
s nímž se může prvý cíl ztotožnit, je fakt rodic(johana, ferdinand). Systém se pak pokouší
v databázi najít fakt rodic(ferdinand, marie). Takový tam není, takže tento pokus skončí
neúspěchem. Další možnou substitucí je X = karel, po níž vyhodnocení dopadne úspěšně. Při
požadavku o další vyhodnocení zadaném středníkem systém už odpovídá no. Odpověď systému
na zadaný dotaz je tedy kladná s tím, že Marie je vnučkou Johany přes svého otce Karla.
Co se stane, když změníme pořadí cílů v dotazu?
?- rodic(X,marie),rodic(johana,X).
Vyhodnocení je pak efektivnější, protože odpadne pokus s navázáním hodnoty ferdinand na
proměnnou X.
Dotaz „Kdo jsou praprarodiče Rudolfa?“ je nejvýhodnější zadat takto:
?- rodic(X,rudolf),rodic(Y,X),rodic(Z,Y).
X = maxmilian, Y = anna, Z = vladislav;
X = maxmilian, Y = ferdinand, Z = johana;
X = marie, Y = karel, Z = johana;
no
Odpověď na danou otázku dává proměnná Z.
Dotaz „Kdo je sestrou Ludvíka?“ může vypadat následovně:
?- rodic(X,ludvik),rodic(X,Y),zena(Y).
X = vladislav, Y = anna;
no
Odpověď na danou otázku dává proměnná Y.
Používání složených dotazů je nevýhodné. Vhodnější je pro podobné účely zavést pravidla.
- 45 -
Jiří Dvořák
Jazyky pro umělou inteligenci
Příklady pravidel (texty počínající znakem % do konce řádku představují poznámky):
% X je prarodicem Y
prarodic(X,Y):- rodic(X,Z),rodic(Z,Y).
% X je sestrou Y
sestra(X,Y):- rodic(Z,Y),rodic(Z,X),zena(X).
Můžeme pak pokládat takovéto dotazy:
% Kdo je prarodicem Marie?
?- prarodic(X,marie).
% Kdo je vnukem nebo vnuckou johany?
?- prarodic(johana,X).
% Kdo je sestrou ludvika?
?- sestra(X,ludvik).
% Ci sestrou je anna?
?- sestra(anna,X).
Uvedené příklady dokumentují skutečnost, že se často můžeme ptát na kteroukoli proměnnou,
která vstupuje do vztahu definovaného pravidlem.
Definujme nyní vztah bratr a ověřme, jak funguje.
% X je bratrem Y
bratr(X,Y):- rodic(Z,Y),rodic(Z,X),muz(X).
% Kdo je bratrem Rudolfa
?- bratr(X,rudolf).
X = rudolf;
X = matyas;
X = rudolf;
X = matyas;
no
Dostali jsme nesmyslnou odpověď, že bratrem Rudolfa je Rudolf. Definice vztahu bratr je tedy
nedokonalá a musí být doplněna o podmínku, že X je různé od Y. K tomu můžeme použít buď
operátor \= (viz str. 51) nebo operátor \== (viz str. 66). Dalším nedostatkem je skutečnost, že
odpovědi jsou zdvojené v důsledku toho, že cíl je splněn jednou přes otce a jednou přes matku.
Pokud chceme obdržet všechny možné odpovědi bez opakování, můžeme to udělat dvěma
způsoby. Do databáze zaznamenáváme nově získaná řešení pomocí predikátu asserta (viz str. 68)
a testujeme jejich přítomnost. Jednodušší možností je použít predikát setof (viz str. 69).
Pravidla podobná výše uvedeným příkladům neumožňují realizovat složitější výpočty. Tuto
možnost však poskytují rekurzivní pravidla. Příkladem může být procedura, definující vztah
předek:
- 46 -
Jiří Dvořák
Jazyky pro umělou inteligenci
% X je predkem Y
predek(X,Y):- rodic(X,Y).
% Trivialni pripad
predek(X,Y):- rodic(X,Z),predek(Z,Y).
% Rekurze
Rekurzívní proceduru musíme definovat tak, aby alespoň jedno pravidlo neobsahovalo rekurzi.
Zkusme nyní položit dotaz
?- predek(X,rudolf).
X = maxmilian ;
X = marie ;
X = vladislav ;
X = johana ;
X = johana ;
X = anna ;
X = ferdinand ;
X = karel ;
no
Připadá-li zkoumaná rodinka nějak podivná či něco připomínající, nechť nahlédne do následující
tabulky. Zjistí, že se jedná o průnik rodokmenů jagellonské, habsburské a kastilsko-aragonské
dynastie. Skutečnost, že Rudolf II. byl dvojnásobným potomkem Johany Šílené, může vysvětlit
některé rysy jeho osobnosti.
Osoba
rok narození
rok úmrtí
Vladislav Jagellonský
1456
1516
český a uherský král
Johana Šílená
1479
1555
dcera Isabely Kastilské a Ferdinanda
Aragonského; manželka Filipa
Habsburského (zemřel r. 1500)
Ludvík Jagellonský
1506
1526
český a uherský král; padl v bitvě u Moháče
Anna Jagellonská
1503
1547
česká a uherská královna; je po ní
pojmenován letohrádek na Pražském hradě
Ferdinand I.
1503
1564
český a uherský král a římský císař
Karel V.
1500
1558
španělský král a římský císař
Maxmilián II.
1527
1576
český a uherský král a římský císař
Marie
1528
1603
Rudolf II.
1552
1612
český a uherský král a římský císař
Matyáš
1557
1619
český a uherský král a římský císař
- 47 -
poznámka
Jiří Dvořák
Jazyky pro umělou inteligenci
Úlohy na procvičení
a) Definujte vztahy strýc, teta, dědeček, babička.
b) Navrhněte vlastní databázi rodinných vztahů a vyzkoušejte definované predikáty.
4.2 Typy a syntaxe dat
Přehled a členění datových objektů Prologu ukazuje obr. 4.2.
datové objekty (termy)
jednoduché objekty
konstanty
atomy
struktury (složené
termy)
proměnné
čísla
Obr. 4.2. Datové objekty v Prologu
Atomy
•
identifikátory začínající malým písmenem; součástí jména atomu může být i znak _
(podtržení), který se syntakticky zařazuje do stejné skupiny znaků jako velká písmena, takže
jím jméno atomu nesmí začínat
•
posloupnosti speciálních znaků
•
řetězce znaků uzavřené mezi apostrofy; v takovém případě může jméno atomu obsahovat
libovolné znaky a může i začínat velkým písmenem
Čísla
Používá se notace běžná v tradičních programovacích jazycích. Všechny implementace Prologu
dovolují používat celých čísel, jen některé mají zavedeny rovněž operace s čísly necelými
zobrazenými v pohyblivé řádové čárce.
Proměnné
Na rozdíl od Lispu, kde proměnnou můžeme vyjádřit libovolným symbolickým atomem, Prolog
syntakticky rozlišuje atomy a proměnné. Proměnné v Prologu jsou reprezentovány identifikátory,
které začínají velkým písmenem nebo znakem _ (podtržení). Zvláštní význam má tzv. anonymní
proměnná vyjádřená pouze znakem _ . Touto proměnnou označujeme takový argument relace,
- 48 -
Jiří Dvořák
Jazyky pro umělou inteligenci
jehož hodnota nás nezajímá. Každý výskyt znaku _ představuje novou anonymní proměnnou,
takže hodnoty na příslušných místech se nemusejí shodovat.
Každá proměnná má svůj rozsah platnosti lexikálně omezen klauzulí, v níž je použita. V jistém
smyslu lze roli proměnných v pravidlech chápat jako obdobu formálních parametrů
v procedurách; přitom způsob volání těchto „parametrů“ je specificky prologovská varianta
volání jménem.
Každá proměnná se může nacházet v jednom z následujících dvou stavů:
•
nenastavená (neinstanciovaná) proměnná není vázána na žádnou konstantu nebo strukturu
jako na svoji hodnotu
•
nastavená (instanciovaná) proměnná má již přiřazenu konkrétní hodnotu
K nastavení proměnných dochází především při výběru pravidla nebo faktu z databáze, podle
něhož se bude Prolog řídit při vyhodnocení určitého cíle. Jednou definovanou hodnotu proměnné
nelze v rozsahu její platnosti měnit.
Ke zrušení nastavení dochází jednak při úspěšném nebo neúspěšném výstupu z lexikálního
rozsahu platnosti dané proměnné, ale také při zpětném návratu a požadavku na nové splnění cíle,
v němž došlo k předchozímu nastavení proměnné.
Struktury
Strukturované objekty neboli struktury jsou vytvořené z dílčích komponent (složek) jejich
spojením do zápisu, který má syntaktickou podobu tzv. složeného termu
f(t1, t2, … , tn)
kde f je atom (identifikátor) vyjadřující funktor struktury a t1, t2, … , tn jsou libovolné datové
objekty vyjadřující argumenty. Strukturu lze chápat jako linearizovaný zápis obecného stromu.
Diskutabilní vlastností Prologu je možnost používat v termech stejné jméno funktoru s rozdílným
počtem argumentů. Z tohoto důvodu se každý funktor vedle svého jména charakterizuje ještě
svojí četností neboli aritou.
Syntaxe Prologu dovoluje vžité binární operátory (např. aritmetické a relační operátory) používat
také v infixovém zápisu.
Příklady struktur:
bod(1,1)
bod(X,Y)
trojuhelnik(bod(1,1),bod(3,1),bod(2,3))
4.3 Syntaxe programu
Program je posloupnost klauzulí (případně doplněná komentáři):
klauzule1
klauzule2
...
- 49 -
Jiří Dvořák
Jazyky pro umělou inteligenci
klauzuleN
Každá klauzule má jeden z těchto tvarů:
term0 :- terml,term2, … ,termM.
(pravidlo)
term.
(fakt)
?- terml,term2, … ,termK.
(dotaz)
Za komentář se v Prologu považuje text začínající znakem % (procento) až do konce řádku.
Každý term použitý v klauzuli má nejčastěji tvar struktury, může to však být také atom. Symboly
používané pro zápis klauzulí je třeba považovat za infixové popř. prefixové operátory, takže i
klauzule jsou syntakticky vzato termy (např. hlavním funktorem pravidla je operátor :-).
Význam symbolů v pravidle: symbol :- znamená implikaci ⇐ a čárka znamená konjunkci (je
také možno použít středník ve významu disjunkce).
Termy uvedené v klauzulích se při výpočtu interpretují jako cíle. Termy lze vytvářet dynamicky i
během výpočtu programu a existuje i způsob, jak takto vytvořené termy interpretovat jako cíle.
Lze tedy konstatovat, že podobně jako v Lispu, také v Prologu nelze syntakticky rozlišit program
od dat a záleží na kontextu použití, zda se určitý term chápe pouze jako datový objekt nebo jako
cíl.
4.4 Unifikace
Vyhodnocovací systém musí určovat, zda určité pravidlo nebo fakt jsou použitelné při
vyhodnocení zadaného cíle. To znamená, že tedy musí být schopen důkladného srovnání hlavy
klauzule a cíle.
Operace srovnání (matching) je jedním ze základních pilířů systému Prolog. Je to aktivní
mechanismus, jehož výsledkem není jen odpověď ano/ne. Během procesu srovnání se totiž
případně musí hledat vhodná substituce za proměnné, pomocí níž se podaří termy "zestejnit".
Dva termy se srovnají když jsou identické, nebo když proměnné vyskytující se v těchto termech
lze nastavit na takové hodnoty, že se získají identické termy .
Protože český termín srovnání má obecnější význam, používá se často místo něj termín unifikace.
Výsledkem unifikace je vždy buď neúspěch, nebo substituce, s jejíž pomocí se termy unifikují, tj.
stanou se identickými. Tato substituce může ovšem být i prázdná, pokud jsou unifikované termy
identické.
Zásady pro unifikaci
Nechť S a T jsou termy:
1. Jsou-li S a T konstanty, pak se unifikují právě tehdy, když se jedná o stejné objekty.
2. Je-li S nenastavená proměnná a T jakýkoliv term, pak se unifikují a současně se S nastaví na
hodnotu T. Podobně, je-li T nenastavená proměnná, pak se nastaví na hodnotu S. Je-li S nebo
T nastavená proměnná, pak se k unifikaci použije její hodnota.
3. Jsou-li S a T struktury, pak se unifikují právě tehdy, když
(a) S i T mají stejný hlavní funktor,
- 50 -
Jiří Dvořák
Jazyky pro umělou inteligenci
(b) odpovídající si složky termů S a T se unifikují.
Výsledkem úspěšné unifikace je vždy tzv. nejobecnější unifikátor, což je substituce, která váže
hodnoty proměnných tím nejméně omezujícím způsobem, a tak jim dává větší volnost pro
případné následující unifikace.
Operátory = a \=
Pomocí operátoru = můžeme explicitně požádat o unifikaci termů. Tento operátor uspěje, pokud
jsou oba termy unifikovatelné. Naopak operátor \= uspěje, když zadané termy nejsou
unifikovatelné.
Vztah bratr (viz str. 46) můžeme nyní pomocí operátoru \= předefinovat takto:
% X je bratrem Y
bratr(X,Y):- rodic(Z,Y),rodic(Z,X),muz(X),X\=Y.
Příklad unifikace:
?- trojuhelnik(bod(1,1),A,bod(2,3))=
trojuhelnik(X,bod(4,5),bod(2,Y)).
A = bod(4,5), X = bod(1,1), Y = 3
4.5 Deklarativní a procedurální sémantika
Deklarativní sémantika programu popisuje vztahy mezi objekty (tzn. CO platí).
Procedurální sémantiku programu určuje způsob, JAK Prolog využívá svoji databázi při
zodpovídání dotazů, jaká je posloupnost dílčích kroků při této činnosti.
Například mějme klauzuli tvaru
P :– Q, R.
Při deklarativním pohledu můžeme tuto klauzuli číst takto:
•
P je pravdivé, jestliže Q i R jsou pravdivé,
•
z platnosti Q a R plyne P, apod.
Při procedurálním pohledu chápeme tuto klauzuli takto:
•
aby se vyřešil problém P, je třeba nejprve vyřešit Q a pak vyřešit R,
•
aby se splnil cíl P, je třeba nejprve splnit cíl Q a potom splnit cíl R.
Procedurální sémantika určuje, že cíle jsou vyhodnocovány v tom pořadí, v jakém jsou zapsány
v dotazu nebo v pravidle, a dále že při vyhodnocování cílů systém prochází klauzule v databázi
v tom pořadí, v jakém jsou zde uloženy.
Vztah bratr můžeme definovat dvěma způsoby:
% X je bratrem Y
bratr1(X,Y):- rodic(Z,Y),rodic(Z,X),muz(X),X\=Y.
bratr2(X,Y):- muz(X),rodic(Z,X),rodic(Z,Y),X\=Y.
- 51 -
Jiří Dvořák
Jazyky pro umělou inteligenci
Z hlediska deklarativní sémantiky jsou oba vztahy rovnocenné, avšak z hlediska procedurální
sémantiky tomu tak není. Záleží totiž na tom, na co se ptáme. Je-li zadáno Y a X je proměnná, pak
je výhodnější prvá varianta, kdežto ve druhém případě je tomu naopak. Sloučit obě varianty do
jedné a ponechat rozhodnutí o tom, co je výhodnější, na systému umožňuje predikát nonvar (viz
str. 65).
Podobně vztah předek (viz str. 47) můžeme definovat dokonce čtyřmi způsoby, které jsou
z hlediska deklarativní sémantiky rovnocenné:
% Varianta 1
predek1(X,Y):- rodic(X,Y).
predek1(X,Y):- rodic(X,Z),predek1(Z,Y).
% Varianta 2
predek2(X,Y):- rodic(X,Y).
predek2(X,Y):- predek2(Z,Y),rodic(X,Z).
% Varianta 3
predek3(X,Y):- rodic(X,Z),predek3(Z,Y).
predek3(X,Y):- rodic(X,Y).
% Varianta 4
predek4(X,Y):- predek4(Z,Y),rodic(X,Z).
predek4(X,Y):- rodic(X,Y).
V důsledku procedurální sémantiky je nejvýhodnější prvá varianta, méně efektivní je druhá
varianta, ještě hůře ja na tom třetí varianta a nejhorší je čtvrtá varianta, která skončí chybovým
hlášením (tato varianta totiž definuje potenciálně nekonečnou rekurzi).
Substituce
Substituci je možno definovat jako konečnou množinu dvojic
σ = {X1 = t1, X2 = t2, … , Xn = tn}
kde Xi jsou proměnné a ti jsou termy, přičemž Xi ≠ Xj a žádný z termů ti neobsahuje proměnné Xj
pro všechna j.
Výsledek substituce σ na term A je term, který se získá dosazením termu ti za každý výskyt
proměnné Xi v termu A. Tento term označme jako Aσ .
Term (klauzule) B je instancí termu (klauzule) A, pokud existuje taková substituce σ , pro kterou
platí B = Aσ .
Variantou termu (klauzule) nazýváme takovou jeho instanci, v níž se každá proměnná nahradila
jinou proměnnou.
Deklarativní sémantika
Deklarativní sémantika programu v Prologu má základ ve vztahu Prologu a predikátové logiky.
Místo pojmu formule se používá pojem cíl.
- 52 -
Jiří Dvořák
Jazyky pro umělou inteligenci
Pro daný program a cíl G stanoví deklarativní sémantika, že cíl G je pravdivý právě tehdy, když
v programu existuje klauzule C taková, že existuje její instance I taková, že
(a) hlava klauzule I je identická s cílem G a současně
(b) všechny cíle z těla klauzule I jsou pravdivé.
Deklarativní sémantika dotazu majícího tvar posloupnosti cílů G1, G2, ..., Gn se definuje
v Prologu tak, že všechny cíle této posloupnosti musejí být pravdivé pro stejnou substituci
proměnných. Substituce získaná jako výsledek zpracování dotazu je nejobecnější takovou
substitucí.
Procedurální sémantika
Procedurální sémantika vyjadřuje, jak Prolog zodpovídá dotazy. Zodpovědět dotaz znamená
pokusit se splnit cíle, které jsou v něm uvedeny, neboli nalézt takovou substituci proměnných
uvedených v těchto cílech, že cíle logicky vyplývají z programu. Procedurální sémantiku Prologu
tedy můžeme vyjádřit procedurou, kterou se vyhodnocuje posloupnost cílů s ohledem na zadaný
program.
Vyhodnocovací proceduru nazvěme Evaluate. Při vyhodnocení posloupnosti cílů postupuje
procedura Evaluate takto:
•
Je-li posloupnost cílů prázdná, vyhodnocení končí úspěchem.
•
Pro neprázdnou posloupnost cílů G1, G2, … , Gm se provádí operace označená jako
prohlížení. Prohlíží se klauzule programu shora dolů až do nalezení první klauzule C
takové, že její hlava se úspěšně unifikuje s cílem G1. Pokud se taková klauzule nenalezne,
končí vyhodnocení neúspěšně.
Operace prohlížení pracuje následovně:
1. Mějme klauzuli C tvaru A :– B1 , B2 , … , Bn . Přejmenují se všechny její proměnné tak,
aby se dostala varianta C' klauzule C, která nemá žádné společné proměnné s cíli G1, G2,
… , Gm .
2. Nechť má C' tvar A' :– B1', B2', … , Bn' . Unifikují se G1 a A'. Je-li unifikace úspěšná, získá
se substituce σ. V posloupnosti cílů se cíl G1 nahradí tělem klauzule C', takže se získá
nová posloupnost cílů ve tvaru B1', B2', … , Bn', G2 , … , Gm . Je-li klauzule C faktem, pak
je n = 0 a posloupnost cílů se tak fakticky zkrátí o jeden cíl, což může posléze vést
k jejímu vyprázdnění a úspěšnému ukončení vyhodnocení.
3. Provede se substituce σ do nového seznamu cílů, takže se získá další tvar B1'σ, B2'σ, … ,
Bn'σ, G2σ , … , Gmσ. Rekurzivním voláním procedury Evaluate se vyhodnotí tato
posloupnost cílů a přitom se průběžně doplňuje vytvářená substituce. Skončí-li toto
vyhodnocení úspěšně, také původní vyhodnocení se považuje za úspěšné. Není-li toto
vyhodnocení úspěšné, provede se návrat k předchozí posloupnosti cílů, přičemž se odstraní
ty části substituce, které byly doplněny při zpracování klauzule C. Dále se pokračuje
v prohlížení bezprostředně za klauzulí C ve snaze nalézt další použitelnou klauzuli.
- 53 -
Jiří Dvořák
Jazyky pro umělou inteligenci
4.6 Blokový model vyhodnocování
Každý cíl, který má být splněn, si můžeme představit jako schránku či blok (box) se čtyřmi
branami (porty), které mají názvy CALL, EXIT, FAIL, REDO.
Brány CALL a REDO jsou vstupní, brány EXIT a FAIL výstupní. Při volání cíle se vstoupí
branou CALL. Je-li cíl splněn, vystoupí se z boxu branou EXIT. Není-li cíl splněn, vystoupí se
branou FAIL.
Když dojde k návratu, tj. při požadavku nalézt alternativní řešení, vstoupí se opět do schránky,
ale branou REDO. Je-li cíl splněn, vystoupí se branou EXIT a není-li splněn, vystoupí se branou
FAIL.
EXIT
CALL
Cíl
REDO
FAIL
Obr. 4.3. Blokový model vyhodnocování
Názvy těchto bran se objevují ve výpisu při ladění programu. Uvažujme tuto definici vztahu
bratr:
% X je bratrem Y
bratr(X,Y):- rodic(Z,Y),rodic(Z,X),muz(X),X\=Y.
Po položení dotazu „Kdo je bratrem Rudolfa?“
?- bratr(X,rudolf).
získáme následující výpis (byl použit systém Amzi!Prolog):
CALL: user:bratr(H13, rudolf)
trying:(1) user:bratr(H13, rudolf)
CALL: user:rodic(H87, rudolf)
trying:(8) user:rodic(maxmilian, rudolf)
EXIT:(8) user:rodic(maxmilian, rudolf)
CALL: user:rodic(maxmilian, H13)
trying:(8) user:rodic(maxmilian, rudolf)
EXIT:(8) user:rodic(maxmilian, rudolf)
CALL: user:muz(rudolf)
trying:(6) user:muz(rudolf)
EXIT:(6) user:muz(rudolf)
CALL: user:(rudolf \= rudolf)
FAIL: user:(rudolf \= rudolf)
REDO: user:muz(rudolf)
- 54 -
Jiří Dvořák
Jazyky pro umělou inteligenci
FAIL: user:muz(rudolf)
REDO: user:rodic(maxmilian, rudolf)
trying:(9) user:rodic(maxmilian, matyas)
EXIT:(9) user:rodic(maxmilian, matyas)
CALL: user:muz(matyas)
trying:(7) user:muz(matyas)
EXIT:(7) user:muz(matyas)
CALL: user:(matyas \= rudolf)
EXIT: user:(matyas \= rudolf)
EXIT:(1) user:bratr(matyas, rudolf)
Čísla v kulatých závorkách jsou pořadová čísla použitých klauzulí v rámci procedur bratr, rodic
(viz str. 43) a muz (viz str. 44). Jestliže však položíme dotaz „Čí bratr je Rudolf?“
?- bratr(rudolf,X).
dostaneme výpis dokumentující, že použití uvedeného pravidla pro tento typ dotazu je méně
výhodné.
CALL: user:bratr(rudolf, H26)
trying:(1) user:bratr(rudolf, H26)
CALL: user:rodic(H99, H26)
trying:(1) user:rodic(vladislav, ludvik)
EXIT:(1) user:rodic(vladislav, ludvik)
CALL: user:rodic(vladislav, rudolf)
FAIL: user:rodic(vladislav, rudolf)
REDO: user:rodic(vladislav, ludvik)
trying:(2) user:rodic(vladislav, anna)
EXIT:(2) user:rodic(vladislav, anna)
CALL: user:rodic(vladislav, rudolf)
FAIL: user:rodic(vladislav, rudolf)
REDO: user:rodic(vladislav, anna)
trying:(3) user:rodic(johana, ferdinand)
EXIT:(3) user:rodic(johana, ferdinand)
CALL: user:rodic(johana, rudolf)
FAIL: user:rodic(johana, rudolf)
REDO: user:rodic(johana, ferdinand)
trying:(4) user:rodic(johana, karel)
EXIT:(4) user:rodic(johana, karel)
CALL: user:rodic(johana, rudolf)
FAIL: user:rodic(johana, rudolf)
REDO: user:rodic(johana, karel)
trying:(5) user:rodic(anna, maxmilian)
EXIT:(5) user:rodic(anna, maxmilian)
CALL: user:rodic(anna, rudolf)
FAIL: user:rodic(anna, rudolf)
REDO: user:rodic(anna, maxmilian)
trying:(6) user:rodic(ferdinand, maxmilian)
EXIT:(6) user:rodic(ferdinand, maxmilian)
CALL: user:rodic(ferdinand, rudolf)
- 55 -
Jiří Dvořák
Jazyky pro umělou inteligenci
FAIL: user:rodic(ferdinand, rudolf)
REDO: user:rodic(ferdinand, maxmilian)
trying:(7) user:rodic(karel, marie)
EXIT:(7) user:rodic(karel, marie)
CALL: user:rodic(karel, rudolf)
FAIL: user:rodic(karel, rudolf)
REDO: user:rodic(karel, marie)
trying:(8) user:rodic(maxmilian, rudolf)
EXIT:(8) user:rodic(maxmilian, rudolf)
CALL: user:rodic(maxmilian, rudolf)
trying:(8) user:rodic(maxmilian, rudolf)
EXIT:(8) user:rodic(maxmilian, rudolf)
CALL: user:muz(rudolf)
trying:(6) user:muz(rudolf)
EXIT:(6) user:muz(rudolf)
CALL: user:(rudolf \= rudolf)
FAIL: user:(rudolf \= rudolf)
REDO: user:muz(rudolf)
FAIL: user:muz(rudolf)
REDO: user:rodic(maxmilian, rudolf)
FAIL: user:rodic(maxmilian, rudolf)
REDO: user:rodic(maxmilian, rudolf)
trying:(9) user:rodic(maxmilian, matyas)
EXIT:(9) user:rodic(maxmilian, matyas)
CALL: user:rodic(maxmilian, rudolf)
trying:(8) user:rodic(maxmilian, rudolf)
EXIT:(8) user:rodic(maxmilian, rudolf)
CALL: user:muz(rudolf)
trying:(6) user:muz(rudolf)
EXIT:(6) user:muz(rudolf)
CALL: user:(rudolf \= matyas)
EXIT: user:(rudolf \= matyas)
EXIT:(1) user:bratr(rudolf, matyas)
4.7 Zpracování čísel
Prolog byl koncipován jako jazyk vhodný pro symbolické výpočty a programování číselných
operací má v něm ještě menší podporu než např. v Lispu. Bez počítání s čísly se však neobejdeme
ani v logickém programování, a tak je celočíselná aritmetika součástí každé implementace
Prologu.
Operátory, které implementace Prologu dovolují používat při zápisu aritmetických výrazů,
zpravidla vyjadřují všechny čtyři základní aritmetické operace (t.j. +, –, *, /), k dispozici mohou
být i operátor celočíselného dělení a zbytku (div a mod). Operátor / pak v takovém případě
vyjadřuje dělení s výsledkem typu real.
Možnost zapisovat v Prologu aritmetické výrazy v infixové notaci ještě neznamená, že
aritmetický výraz se vždy vyhodnocuje. Pro vyhodnocení aritmetických výrazů slouží operátor is,
který patří mezi tzv. standardní neboli zabudované predikáty Prologu.
- 56 -
Jiří Dvořák
Jazyky pro umělou inteligenci
Operátor is
Predikát is funguje do určité míry podobně jako operátor přiřazení v imperativních
programovacích jazycích. Nejprve se vyhodnotí aritmetický výraz na pravé straně. Pak se ale
neprovede běžné přiřazení vzniklé hodnoty, nýbrž její unifikace s proměnnou na straně levé.
Pokud tato proměnná zatím není nastavena na konkrétní datovou hodnotu, získá hodnotu výrazu
a is skončí úspěšně. Pokud již proměnná hodnotu má, pak se musí rovnat spočtené hodnotě
výrazu, jinak is skončí neúspěchem.
Samozřejmým předpokladem úspěšného vyhodnocení výrazu na pravé straně je to, aby všechny
proměnné obsažené v tomto výrazu měly v době vyhodnocení definovanou číselnou hodnotu.
Pokud tato podmínka není splněna, vyhodnocení skončí chybovým hlášením (nikoliv tedy
mechanismem neúspěchu).
Relační operátory
Vedle aritmetických operátorů jsou v Prologu k dispozici také následující infixové binární
operátory umožňující srovnání číselných hodnot vyjádřených aritmetickými výrazy:
<
menší než
=<
menší nebo rovno
=:=
rovno
>
větší než
>=
větší nebo rovno
=\=
různé
Tyto operátory slouží v Prologu výhradně ke srovnání číselných hodnot a způsobí automatické
vyhodnocení svých operandů na obou stranách.
Následující příklad ukazuje rozdíl mezi operátorem =:= a operátorem = , který slouží ke srovnání
(unifikaci) termů.
?-3*4=:=4+8.
yes
?-3*4=4+8.
no
?-3*X=Y*6.
X=6, Y=3
Příklad: Predikát pro výpočet faktoriálu
% Vztah faktorial(N,F) znamena, ze faktorialem N je F
faktorial(0,1).
faktorial(N,F):- N>0,
N1 is N-1,
- 57 -
Jiří Dvořák
Jazyky pro umělou inteligenci
faktorial(N1,F1),
F is N*F1.
Vraťme se nyní k programu z podkap. 4.1 (viz str. 43) a zaveďme do něj údaje o narození a
úmrtí:
narozeni(vladislav, 1456).
narozeni(johana, 1479).
narozeni(ludvik, 1506).
narozeni(anna, 1503).
narozeni(ferdinand, 1503).
narozeni(karel, 1500).
narozeni(maxmilian, 1527).
narozeni(marie, 1528).
narozeni(rudolf, 1552).
narozeni(matyas, 1557).
umrti(vladislav, 1516).
umrti(johana, 1555).
umrti(ludvik, 1526).
umrti(anna, 1547).
umrti(ferdinand, 1564).
umrti(karel, 1558).
umrti(maxmilian, 1576).
umrti(marie, 1603).
umrti(rudolf, 1612).
umrti(matyas, 1619).
Nyní můžeme např. definovat vztah mladší
% X je mladsi nez Y
mladsi(X,Y):-narozeni(X,Rx),narozeni(Y,Ry),Rx>Ry.
a položit dotaz, zda měl Rudolf mladšího bratra:
?-bratr(X,rudolf),mladsi(X,rudolf).
X=matyas
Úlohy na procvičení
a) Definujte predikát pro určení maxima ze dvou čísel (viz s. 83).
b) Definujte predikát pro výpočet největšího společného dělitele (viz s. 83).
4.8 Zpracování seznamů
Seznam v Prologu má tvar
[t1, t2, … , tn]
kde ti jsou termy.
Příklady seznamů:
- 58 -
Jiří Dvořák
Jazyky pro umělou inteligenci
[a,b,c,d]
[a,[b,c],d]
[]
Vnitřní reprezentace seznamu odpovídá složenému termu
.(t1, [t2, … , tn])
kde . (tečka) je speciální binární funktor, jehož prvním argumentem je první prvek seznamu a
druhým argumentem je zbytek seznamu. Tato struktura je tedy analogií lispovské tečky-dvojice.
Na rozdíl od Lispu atom nil nevyjadřuje prázdný seznam. Prázdný seznam se v Prologu dá
vyjádřit pouze takto: []
Další analogií tečky dvojice je zápis
[1.prvek seznamu |zbytek seznamu]
Tento způsob zápisu je možno zobecnit tak, že před znakem | se může nacházet i více prvků
seznamu.
Seznam
[t1,t2, … ,tn]
je tedy možno zapsat také těmito způsoby:
[t1|[t2, … ,tn]]
[t1,t2|[t3, … ,tn]]
[t1,t2,t3|[t4, … ,tn]]
...
[t1,t2, … ,tn|[]]
Například seznam [a,b,c,d] můžeme zapsat také takto:
[a|[b,c,d]]
[a,b|[c,d]]
[a,b,c|[d]]
[a,b,c,d|[]]
V podkapitole 2.5 jsme definovali funkci Lispu pro spojení dvou seznamů (viz str. 18).
Podívejme se, jak se tento problém vyřeší v Prologu.
% Vztah spoj(X,Y,Z) znamena, ze Z je spojenim seznamu X a Y
spoj([],X,X).
spoj([H|T],X,[H|NT]):- spoj(T,X,NT).
Predikát spoj má o jeden argument více, než lispovská funkce Spoj, protože se jedná o definici
vztahu mezi zadanými seznamy a výsledným seznamem. Dále si ve druhé klauzuli povšimněme
- 59 -
Jiří Dvořák
Jazyky pro umělou inteligenci
důležité možnosti Prologu používat definice vzorem argumentu. Dalším rozdílem oproti Lispu je
to, že predikát spoj můžeme použít „v obou směrech“, jak ukazují následující příklady.
?-spoj([a,b],[c,d],X).
X=[a,b,c,d]
?-spoj([a,b],X,[a,b,c,d]).
X=[c,d]
?-spoj(X,[c,d],[a,b,c,d]).
X=[a,b]
?-spoj(X,Y,[a,b,c,d]).
X=[], Y=[a,b,c,d];
X=[a], Y=[b,c,d];
X=[a,b], Y=[c,d];
X=[a,b,c], Y=[d];
X=[a,b,c,d], Y=[];
no
Další příklady práce se seznamy:
•
Test, zda X je prvkem daného seznamu
clen(X,[X|_]).
clen(X,[_|Zbytek]):- clen(X,Zbytek).
V tomto příkladu si povšimněme použití anonymní proměnné.
•
Obrácení seznamu na nejvyšší úrovni pomocí predikátu spoj
obrat([],[]).
obrat([X|L],R):- obrat(L,S),spoj(S,[X],R).
•
Efektivnejsi verze obraceni seznamu pomocí koncové rekurze
obrat1(T,R):- obrat2(T,[],R).
obrat2([],P,P).
obrat2([H|T],P,R):- obrat2(T,[H|P],R).
•
Generování seznamu čísel ze zadaného intervalu
od_do(M,N,[]):- M>N.
od_do(M,N,[M|K]):- M=<N, M1 is M+1, od_do(M1,N,K).
•
Převod prvků libovolně strukturovaného seznamu do jedné úrovně
- 60 -
Jiří Dvořák
Jazyky pro umělou inteligenci
zplosti1([],[]).
zplosti1([H|T],Lst):- zplosti1(H,Lh),zplosti1(T,Lt),
spoj(Lh,Lt,Lst).
zplosti1(X,[X]).
Nedostatkem této verze je to, že při opakovaném vyhodnocováni vytváří víceúrovňové
seznamy. Tento nedostatek může být odstraněn pomocí typového predikátu atomic (viz str.
63).
Úlohy na procvičení
a) Pomocí predikátu clen definujte predikát pro přidání prvku X do seznamu bez opakování
(viz s. 84).
b) Definujte predikát pro přidání prvku X na konec seznamu (viz s. 84).
c) Definujte predikát pro odstranění jednoho výskytu prvku X ze seznamu (viz s. 84).
d) Definujte predikát pro získání posledního prvku seznamu (viz s. 84).
e) Definujte predikát pro odstranění posledního prvku seznamu (viz s. 84).
f) Definujte predikáty pro získání posledního prvku seznamu a odstranění posledního prvku
seznamu pomocí predikátu pro přidání prvku na konec seznamu (viz s. 84).
g) Definujte predikát pro určení délky seznamu (viz s. 84).
4.9 Operátorová notace
Prolog dovoluje při zápisu aritmetických výrazů používat namísto důsledné funkcionální
prefixové notace také obvyklou notaci infixovou. Tato možnost se však neomezuje pouze na
standardní aritmetické a číselné relační operátory, neboť Prolog poskytuje i prostředky, pomocí
nichž může uživatel zavést své vlastní binární nebo unární operátory a používat je při zápisu
struktur.
Je třeba zdůraznit, že zavedení operátoru rozšiřuje výhradně možnosti vstupní syntaxe termů
Prologu. S definovaným operátorem není spojena žádná operace, která by se měla provádět
s příslušnými argumenty. Operátorová notace dává uživateli pouze možnost učinit program nebo
data v Prologu čitelnější. Definované operátory se chápou jako jiné funktory s tím, že při jejich
použití se vyjadřuje složený term jinou notací, než která platí pro běžné funktory.
Definice nových operátorů se provádí pomocí speciálních klauzulí nazývaných direktivy.
Direktiva definující operátor se musí systému Prolog zadat dříve, než se tento operátor použije
v nějakém výrazu. Direktiva má tento tvar:
:–op(precedence,typová_specifikace,operátor).
Jména operátorů musejí být atomy, jejich precedence se vyjadřuje číselně a přípustný rozsah je
zpravidla implementačně závislý (typicky od 1 do 1200). V Prologu platí zásada, že čím je větší
hodnota precedence, tím je slabší vazba mezi operátorem a jeho argumenty. Precedence
operátorů a typová specifikace musejí být určeny proto, aby bylo možné rozpoznat strukturu
výrazu s několika operátory.
- 61 -
Jiří Dvořák
Jazyky pro umělou inteligenci
Druhy typových specifikací:
•
pro infixové operátory: xfx, xfy, yfx
•
pro prefixové operátory: fx, fy
•
pro postfixové operátory: xf, yf
Kódy x a y označují argumenty, kód f definovaný operátor.
Kódy x a y určují asociativnost zaváděného operátoru podle následující konvence. Pro každý
složený term se jeho precedencí rozumí precedence jeho hlavního funktoru. Jednoduchý objekt
nebo výraz v závorkách má precedenci 0. Symbol x představuje argument s precedencí ostře
menší než je precedence zaváděného operátoru f, symbol y představuje argument s precedencí
menší nebo rovnou precedenci operátoru f.
Typová specifikace xfx pro nějaký operátor θ znamená, že není povolen zápis a θ b θ c.
Specifikace yfx znamená, že při unifikaci termu a θ b θ c s termem P θ Q se na proměnnou P
naváže term a θ b a na proměnnou Q term c.
Příklad zavedení operátorů:
:-op(600,xfx,pije).
:-op(600,xfx,hraje).
:-op(500,yfx,i).
:-op(700,yfx,a).
karel pije pivo.
robert hraje golf.
tonda pije rum i pivo a jana hraje karty.
Na základě takto zavedených faktů můžeme pokládat následující dotazy:
?- X pije pivo.
X = karel
?- robert hraje X.
X = golf
?- tonda pije X a Z hraje karty.
X = rum i pivo, Z = jana
Následující tabulka ukazuje precedence a typové specifikace standardních operátorů Prologu.
- 62 -
Jiří Dvořák
Jazyky pro umělou inteligenci
Precedence specifikace operátory
1200
xfx
:–
1200
fx
:– , ? –
1100
xfy
;
1000
xfy
,
700
xfx
= , is , < , > , =< , >= , = = , =\= , \= = , =:=
500
yfx
+, –
500
fx
+ , – , not
400
yfx
* , / , div
300
xfx
mod
4.10 Typové predikáty
Proměnné v Prologu nemají definovaný typ a tedy je v době výpočtu možné proměnnou vázat na
datový objekt libovolné povahy. Standardní tzv. typové predikáty umožňují rozeznat typ
navázaného datového objektu.
integer(X)
… uspěje, když X je navázáno na celé číslo
atom(X)
… uspěje, když X je navázáno na atom
constant(X) … uspěje, když X je navázáno na konstantu (číslo nebo atom)
compound(X) … uspěje, když X je navázáno na složený term
Pokud má konkrétní dialekt Prologu zavedeny ještě další typy dat (např. reálná čísla), pak pro ně
má také odpovídající typový predikát. Namísto constant se lze setkat také s predikátem atomic
stejného významu a místo compound(X) lze použít not atomic(X).
Příklady:
integer(123).
yes
atom(123).
no
atom(jan).
yes
atomic(123).
yes
atomic(bod(1,1)).
- 63 -
Jiří Dvořák
Jazyky pro umělou inteligenci
no
compound(rodic(max,ruda)).
yes.
Následující příklad ukazuje převod prvků libovolně strukturovaného seznamu do jedné úrovně
pomocí predikátů atomic a spoj (viz str. 59).
zplosti2([],[]).
zplosti2([H|T],Lst):-zplosti2(H,Lh),zplosti2(T,Lt),
spoj(Lh,Lt,Lst).
zplosti2(X,[X]):-atomic(X),X\==[].
4.11 Rozklad a vytváření termů
Predikát =..
Standardní predikát (operátor) =.. umožňuje převést každý term do podoby, kterou má zápis
funkce v Lispu – tedy do tvaru seznamu, jehož prvním prvkem je hlavní funktor termu a dalšími
prvky jsou jeho argumenty. Operátor se používá podle schématu
Term =.. Seznam
Převod lze provádět v obou směrech.
Příklady:
?- bod(1,2) =.. X.
X = [bod,1,2]
?- a+b =..X.
X = [+,a,b]
?- X =.. [a,b,c,d].
X = a(b,c,d)
Predikáty arg a functor
Pomocí predikátů functor/3 a arg/3 lze odděleně získat buď samotný funktor termu, nebo
libovolný z jeho argumentů.
Cíl
functor(Term,F,Arita)
uspěje, pokud je v termu Term hlavním funktorem F s četností Arita .
Příklady:
?- functor(bod(1,2),F,N).
F = bod, N = 2
?- functor(a+b,F,N).
- 64 -
Jiří Dvořák
Jazyky pro umělou inteligenci
F = +, N = 2
?- functor([a,b,c],F,N).
F = ., N = 2
?- functor(jana,F,N).
F = jana, N = 0
Cíl
arg(N,Term,Arg)
uspěje, pokud N-tým argumentem termu Term je Arg. Tento predikát musí být vždy užit s prvými
dvěma proměnnými nastavenými.
Příklady:
?- arg(2,rodic(jana,karel),X).
X = karel
?- arg(1,a+b+c,X).
X = (a+b)
?- arg(2,[a,b,c],X).
X = [b,c]
Výsledek druhého dotazu odpovídá tomu, že operátor + má typovou specifikaci yfx.
4.12 Meta-logické predikáty
Meta-logické predikáty jsou takové standardní prostředky Prologu, které přesahují rámec
vymezený predikátovou logikou prvního řádu. Prostřednictvím těchto predikátů se můžeme
dotazovat na okamžitý stav vyhodnocení cílů, pracovat s proměnnými (a ne jen s jejich
hodnotami) jako objekty jazyka a dokonce předávat k vyhodnocení jako cíle dynamicky
vytvořené struktury.
Predikáty var a nonvar
Standardní prologovské predikáty var/l a nonvar/l provádějí rozlišení, zda proměnná je nastavena
na konkrétní datový objekt či nikoliv.
Cíl
var(X)
uspěje, právě když proměnná X není nastavena na žádný datový objekt.
Příklad:
?- var(X).
yes
?- X=Z,Z=23,var(X).
no
- 65 -
Jiří Dvořák
Jazyky pro umělou inteligenci
Cíl
nonvar(X)
uspěje, právě když proměnná X je nastavena na nějaký datový objekt.
Predikát nonvar můžeme využít pro takovou, definici vztahu bratr, která zajistí efektivní
vyhodnocení nezávisle na tom, který z argumentů bude v dotazu zadán.
bratr(X,Y):-nonvar(X),muz(X),rodic(Z,X),rodic(Z,Y),X\=Y.
bratr(X,Y):-nonvar(Y),rodic(Z,Y),rodic(Z,X),muz(X),X\=Y.
Predikáty == a \==
Binární predikáty == a \== umožňují zjistit, zda jsou či nejsou dvě proměnné vázány na
identický objekt.
X == Y
uspěje pokud X a Y jsou identické konstanty, identické proměnné, nebo struktury s identickým
funktorem, četností a po řadě identickými argumenty.
X \== Y
selže právě tehdy, jsou-li X a Y identické termy.
Následující příklad ukazuje, jak můžeme v Prologu chápat vztah stejné:
stejne1(X,X).
%unifikovatelne
stejne2(X,Y):- X==Y.
%identicke
Predikát call
V Prologu stejně jako v Lispu mají program i data stejnou syntaxi. V Lispu je možné jakýkoliv
dynamicky vytvořený symbolický výraz považovat za „program“ a vyhodnotit jej pomocí eval.
V Prologu k tomuto účelu slouží standardní predikát call.
Při vyhodnocení cíle call (X) musí být hodnotou argumentu vyhodnotitelný cíl; systém pak
vyhodnotí tento cíl a jeho výsledek je i výsledkem cíle call (X).
Hlavní použití predikátu call spočívá v možnosti interpretace dynamicky vytvářených termů,
které se např. zadávají interaktivně v době výpočtu programu.
Příklad:
eval_list(L):- X=..L, call(X).
?- eval_list([is,Z,2*3]).
Z = 6
?- eval_list([spoj,[a,b],[c,d],X]).
X = [a, b, c, d]
Predikát spoj je definován na str. 59.
- 66 -
Jiří Dvořák
Jazyky pro umělou inteligenci
4.13 Řez
Řez je standardní predikát, který se zapisuje znakem ! (vykřičník) jako jeden z podcílů v těle
pravidla. Pomocí řezu můžeme zabránit tomu, aby se při vyhodnocení cíle zbytečně zkoušela
použít pravidla, o nichž je předem známo, že již k řešení nevedou. Řez tedy redukuje
prohledávací prostor prologovského programu tak, že dynamicky odřezává celé neužitečné
podstromy.
Význam řezu je možné popsat pouze pomocí procedurální sémantiky programu, takže jeho
použitím se od sebe může velmi nebezpečně vzdálit deklarativní a procedurální sémantika.
V souvislosti s tím se rozlišují dva typy řezu:
•
Zelený řez neovlivňuje výslednou množinu řešení cíle a odřezává tedy jen větve neobsahující
potenciální řešení,
•
Červený řez zvyšuje efektivnost programu za cenu nesouladu mezi deklarativní a
procedurální sémantikou.
Řez má následující sémantiku:
•
bezpodmínečně uspěje,
•
fixuje všechny vazby proměnných provedené od unifikace nadřazeného cíle s hlavou
pravidla, v němž se řez nachází,
•
dojde-li v těle pravidla za řezem k selhání nějakého podcíle, které nejde napravit návraty do
podcílů vpravo od řezu, znamená to současně i selhání nadřazeného cíle – žádná další
pravidla se pro něj již neuplatní.
Působením řezu se tedy odstřihnou všechna možná řešení podcílů nacházejících se nalevo od něj
a možnost použití všech pravidel dané procedury, která následují za pravidlem obsahujícím řez.
Na druhé straně se jím neovlivňuje možné navracení v podcílech nacházejících se napravo od něj.
Jakmile by však mělo dojít ke zpětnému návratu před řez, provede se výstup z celé procedury se
signalizací neúspěchu cíle.
Příklady:
•
Přidání prvku do seznamu bez opakování
clen(X,[X|L]).
clen(X,[Y|L]):- clen(X,L).
pridej(X,L,L):- clen(X,L),!.
pridej(X,L,[X|L]).
•
Definice vztahu různé
ruzne1(X,X):- !,fail.
ruzne1(X,Y).
%neunifikovatelne
ruzne2(X,Y):- X\==Y.
%neidenticke
- 67 -
Jiří Dvořák
Jazyky pro umělou inteligenci
4.14 Negace
Negace je v Prologu vyjádřena standardním predikátem not a její význam můžeme ilustrovat
pomocí takto definovaného predikátu neplatí:
neplati(X):- call(X),!,fail.
neplati(X).
kde fail je standardní predikát, který nelze splnit.
Cíl not(X) tedy selže, pokud je cíl X prologovsky splnitelný, a naopak cíl not(X) bude úspěšný,
pokud cíl X splnit nelze.
Nemožnost splnění cíle může ovšem v Prologu vyvolat zacyklení a pak se zacyklí i vyhodnocení
negace, místo aby uspělo. K potížím s negací nedojde, pokud se při vyhodnocení nevyskytují
v negovaném cíli nenastavené proměnné.
Pozn.: Funktor not je standardně definován jako unární prefixový operátor se stejnou preferencí
jakou mají binární operátory + a –, takže není třeba jeho argument dávat do závorek.
Příklad: Přidání prvku X do seznamu bez opakování
clen(X,[X|L]).
clen(X,[Y|L]):- clen(X,L).
pridej_bez_opak(X,L,L):- clen(X,L).
pridej_bez_opak(X,L,[X|L]):- not clen(X,L).
4.15 Práce s databází
Predikáty consult a reconsult
Tyto predikáty zajišťují vstup programu ze souboru.
consult(F)
Vyhodnocení tohoto cíle způsobí načtení klauzulí všech procedur ze souboru F a jejich uložení
do databáze. Stejným způsobem lze načíst procedury z několika souborů, pravidla se ukládají do
databáze v tom pořadí, v jakém se četla. Namísto několika volání predikátu consult je možné
uvést v jeho argumentu seznam jmen souborů obsahujících program.
reconsult(F)
Vyhodnocení tohoto cíle má podobný účinek s tím rozdílem, že všechny procedury predikátů
uvedené v souboru F nahradí případné dosud platné procedury z databáze.
Predikáty assert a retract
Tyto predikáty umožňují explicitní práci s databází.
assert(C)
Tento cíl uspěje pouze jednou a jako vedlejší efekt doplní klauzuli C do databáze a to v závislosti
na implementaci buď jako první nebo jako poslední v odpovídající proceduře. Predikát asserta
- 68 -
Jiří Dvořák
Jazyky pro umělou inteligenci
doplňovanou klauzuli umístí jako první klauzuli příslušné procedury a predikát assertz ji umísí
jako poslední. Klauzule C se zapisuje takto:
nebo (pravidlo)
fakt
přičemž fakt ani pravidlo nejsou zakončeny tečkou.
Příklad: Určení bratra bez opakování
bratr_bez_opak(X,Y):bratr(X,Y),not(nalezen(X,Y)),asserta(nalezen(X,Y)).
Cíl
retract(C)
uspěje při nalezení prvé klauzule v databázi, která se unifikuje s klauzulí C, a z databáze ji
vypustí. Při opakovaném vyhodnocení uspěje, pokud se v databázi nachází další taková klauzule.
Predikát retractall vypustí všechny takové klauzule najednou. Klauzule C se zapisuje stejně jako
v predikátu assert.
Predikát clause
Predikát clause zajišťuje nedestruktivní přístup ke klauzulím databáze. Při vyhodnocení cíle
clause(Hlava,Tělo)
s nastavenou hodnotou proměnné Hlava se hledá v databázi klauzule, jejíž hlava se unifikuje
s hodnotou prvního argumentu. Tělo této klauzule se pak unifikuje s druhým argumentem. Při
unifikaci faktů se za tělo považuje predikát true. Klauzule z databáze nelze hledat pomocí jejich
těla. Při zpětném navracení uspěje predikát clause tolikrát, kolikrát je možné unifikovat klauzuli
z databáze se dvojicí argumentů. Při každé unifikaci klauzule se vytváří nová sada proměnných.
Predikát bagof
Tento predikát umožňuje získat všechna možná řešení současně.
bagof(X,P,L)
Vyhodnocením tohoto cíle se vytvoří seznam L všech objektů X takových, že je cíl P splněn
(mezi X a P by měla existovat nějaká vazba vyjádřená společnými proměnnými).
Příklad: Určení všech bratrů bez opakování
bratr_bez_opak(X,Y):bratr(X,Y),not(nalezen(X,Y)),
asserta(nalezen(X,Y)).
vsichni_bratri1(X,L):bagof(Y,bratr_bez_opak(Y,X),L),
retractall(nalezen(Y,X)),!.
Predikát setof
setof(X,P,L)
- 69 -
Jiří Dvořák
Jazyky pro umělou inteligenci
Funguje podobně jako bagof, avšak seznam L je uspořádaný a bez případných duplicit.
Uspořádání je pro atomy abecední, pro čísla podle hodnoty, a pro složené termy se řídí hlavním
funktorem (případně nejvyšším rozdílným funktorem nebo argumentem zleva).
Příklad: Určení všech bratrů bez opakování
vsichni_bratri2(X,L):-setof(Y,bratr(Y,X),L).
Predikát findall
findall(X,P,L)
Funguje podobně jako bagof s tím rozdílem, že do seznamu L se dostanou všechny hodnoty X,
které splňují cíl P pro libovolné hodnoty jeho volných proměnných.
4.16 Vstup a výstup
Systém předpokládá existenci aktuálního vstupního souboru (standardně klávesnice) a aktuálního
výstupního souboru (standardně obrazovka terminálu). Při provádění základních operací vstupu a
výstupu není třeba explicitně specifikovat, o jaké zařízení (soubor) se jedná.
Základní operace vstupu a výstupu předpokládají sekvenční způsob přístupu k datům souborů.
Soubory mají standardní textový charakter.
Pokud mají zpracovávaná data tvar vyhovující syntaxi termů, používají se pro jejich vstup a
výstup standardní predikáty read a write. Jestliže je třeba vycházet z obecněji strukturovaných
dat, pak jsou k dispozici standardní predikáty get, get0 a put pro vstup a výstup znaků.
Přesměrování vstupu
see(Jméno_souboru)
Tento predikát se splní, je-li daným jménem určen existující soubor. V tom případě je vedlejším
efektem otevření příslušného souboru a přesměrování vstupu na tento soubor. Klávesnice má pro
účely této identifikace stanoveno dohodnuté jméno user.
seeing(F)
Vyhodnocením tohoto cíle se proměnná F nastaví na jméno aktuálního vstupního souboru.
seen
Tento predikát způsobí zavření aktuálního vstupního souboru a přepnutí vstupu na standardní
vstupní soubor.
Přesměrování výstupu
tell(Jméno_souboru)
Tento predikát přesměruje výstup na určený výstupní soubor. Obrazovka má pro účely této
identifikace stanoveno dohodnuté jméno user.
telling(F)
Vyhodnocením tohoto cíle se proměnná F nastaví na jméno aktuálního výstupního souboru.
told
- 70 -
Jiří Dvořák
Jazyky pro umělou inteligenci
Tento predikát způsobí uzavření aktuálního výstupního souboru a přepnutí výstupu na standardní
výstupní soubor.
Vstup termů
read(X)
Tento predikát způsobí načtení dalšího termu z aktuálního vstupního souboru (každý term zde
musí být zakončen tečkou a mezerou nebo přechodem na nový řádek). Přečtený term se pak
unifikuje s argumentem X a pokud je jím nenastavená proměnná, provede se nastavení X na právě
přečtený term.
Pokud unifikace nebyla úspěšná, cíl read(X) selže a to způsobí zpětný návrat a požadavek na
splnění předchozího cíle, neboť read samotný je deterministický predikát a pro každé
vyhodnocení poskytuje jen jedno řešení nebo selhání.
Speciální situace nastane, pokud se při pokusu o vstup termu dosáhlo konce vstupního souboru –
v takovém případě se proměnná X nastaví na hodnotu end_of_file.
Výstup termů
write(X)
Tento predikát způsobí výstup termu X na aktuální výstupní soubor. Term se zobrazí ve
standardní podobě odpovídající syntaktické definici termů.
Pro ovládání tvaru výstupu jsou k dispozici následující predikáty:
tab(N)
provede zápis N mezer
nl
způsobí přechod na nový řádek
Vstup a výstup znaků
get0(C)
Provede se vstup jednoho znaku z aktuálního vstupního souboru přičemž proměnná C se nastaví
na číselnou hodnotu ASCII kódu přečteného znaku.
get(C)
Vyhodnocením tohoto predikátu se čtecí pozice ve vstupním souboru posune až za první
tisknutelný znak a číselná hodnota ASCII kódu tohoto znaku se stane hodnotou proměnné C.
put(C)
Do aktuálního výstupního souboru se provede záznam znaku, jehož ASCII kód má číselnou
hodnotu C.
4.17 Cykly
Iterativní operace je možné v Prologu provádět prostřednictvím koncové rekurze. Prolog má ale
pro vyjádření iterace ještě další možnost, která se podobá repeat-until cyklu v imperativním
- 71 -
Jiří Dvořák
Jazyky pro umělou inteligenci
programování. Opakování se v těchto cyklech vyvolává selháním a jsou užitečné pouze ve
spojení s predikáty, které mají nějaký vedlejší účinek.
Nalezení všech možných řešení zadaného cíle se může zajistit takto:
vsechna_reseni(C,X):call(C),write(X),nl,fail.
vsechna_reseni(C,X).
Proměnná X se musí vyskytovat v cíli C.
Příklad (vztah predek je definován na str. 47):
vsichni_predci(predek(X,Y),X):call(predek(X,Y)),write(X),nl,fail.
vsichni_predci(predek(X,Y),X).
Predikát repeat
Predikát repeat je při volání okamžitě splněn a je také vždy splněn při navracení. Tento predikát
se chová tak, jako by byl definován programem
repeat.
repeat:-repeat.
Cyklus s podmínkou:
repeat,C1,C2, … ,Cn,Podm,!.
C1, C2, … , Cn jsou cíle, které se mají splnit a Podm je podmínka ukončení cyklu.
Příklad:
vstup_a_vypis:repeat,write('Zadej cislo:'),
nl,read(N),N1 is 10*N,
write(N1),nl,N<0,!.
- 72 -
Jiří Dvořák
Jazyky pro umělou inteligenci
5. Využití jazyka Prolog pro řešení úloh umělé inteligence
5.1 Přehled aplikací
Kniha [1] popisuje aplikace Prologu v následujících oblastech UI:
•
heuristické hledání
•
expertní systémy a reprezentace znalostí
•
plánování
•
zpracování přirozeného jazyka
•
strojové učení
•
hraní her
V knize [13] jsou ukázky použití Prologu při řešení následujících problémů:
•
strategie prohledávání
•
plánování
•
implementace pravidlového expertního systému
•
implementace sémantických sítí a rámců
•
strojové učení
•
zpracování přirozeného jazyka
V knize [5] jsou ukázky použití Prologu pro prohledávání a pro zpracování přirozeného jazyka).
Kniha [20] obsahuje popis implementace prohledávání. V knize [19] jsou uvedeny ukázky použití
Prologu pro hraní her a tvorbu expertního systému.
Významnou a častou aplikací Prologu je tvorba expertních systémů. Této problematice jsou
věnovány např. knihy [3] a [15]. Příkladem implementace exxpertního systému v Prologu je
systém FLEX firmy Logic Programming Associates. Jedná se programové prostředí, umožňující
používat pravidla a rámce pro reprezentaci znalostí a dopředné a zpětné řetězení pro inferenci.
Další aplikace Jazyka Prolog je možno najít na stránkách firem Amzi!® a Applied Logic
Systems, Inc.
5.2 Příklad využití Prologu pro tvorbu expertního systému
Vyhodnocovací mechanismus Prologu funguje vlastně jako inferenční mechanismus zpětného
řetězení v pravidlovém expertním systému, kdy se postupuje od cílových hypotéz k výchozím
faktům. Na rozdíl od Lispu tedy při aplikaci Prologu pro tvorbu expertního systému není nutné
programovat žádný infernční mechanismus a prologovský program může představovat bázi
znalosí a bázi faktů.
Příklad použití Prologu pro tvorbu jednoduchého expertního systému popsaný v této podkapitole
vychází z knihy [1] a je rozšířen o možnost komunikace s uživatelem.
- 73 -
Jiří Dvořák
Jazyky pro umělou inteligenci
Jedná se o systém, který diagnostikuje příčiny mokra v domě. Zkoumány jsou tři místnosti: hala,
koupelna a kuchyně. Z haly vedou dveře do koupelny a do kuchyně. V kuchyni se nachází okno.
Situaci můžeme popsat pomocí následujících pravidel:
unik_vody_v_koupelne :- hala_mokra, kuchyne_sucha.
problem_v_kuchyni :- hala_mokra, koupelna_sucha.
zadna_voda_zvenku :- not dest; okno_zavrene.
unik_vody_v_kuchyni :- problem_v_kuchyni, zadna_voda_zvenku.
Počáteční fakty:
hala_mokra.
koupelna_sucha.
okno_zavrene.
Systému můžeme pokládat dotazy
?- unik_vody_v_koupelne.
no
?- unik_vody_v_kuchyni.
yes
Nedostatkem tohoto systému je skutečnost, že uživatel musí zadat všechna relevantní fakta ještě
před zahájením procesu usuzování. Fakta by se měla zadávat interaktivně v dialogu s expertním
systémem v okamžiku, kdy jsou zapotřebí.
Uvedený nedostatek můžeme odstranit definováním dotazovací procedury ask:
ask(X):- known(ano,X),!.
ask(X):- known(_,X),!,fail.
ask(X):- write(X),write('?(ano/ne):'),
read(Y),asserta(known(Y,X)),Y=ano.
Pak musíme doplnit do výše uvedeného programu tato pravidla:
hala_mokra:- ask(hala_mokra).
kuchyne_sucha:- ask(kuchyne_sucha).
koupelna_sucha:- ask(koupelna_sucha).
dest:-ask(dest).
okno_zavrene:- ask(okno_zavrene).
Nová syntaxe pravidel a faktů
Dalším nedostatkem výše uvedeného systému je špatná čitelnost znalostí, vyplývající z použití
prologovské syntaxe. Předpokládejme, že bychom chtěli používat následující tvar pravidel, který
je pro pravidlové expertní systémy obvyklejší:
- 74 -
Jiří Dvořák
Jazyky pro umělou inteligenci
if podmínka then závěr
Podmínka může být složena z jednoduchých podmínek nebo jejich negací spojených pomocí
spojek and a or, přičemž negace je zapsána pomocí operátoru ne.
Tuto notaci nám umožní zavedení nových operátorů
:-op(800,fx,if).
:-op(700,xfx,then).
:-op(300,xfy,or).
:-op(200,xfy,and).
:-op(100,fx,ne).
Fakta budeme zapisovat následovně:
fact(tvrzení).
Pak mohou být pravidla popisující příčiny mokra v domě přepsána takto:
if hala_mokra and kuchyn_sucha then unik_vody_v_koupelne.
if hala_mokra and koupelna_sucha then problem_v_kuchyni.
if ne dest or okno_zavrene then zadna_voda_zvenku.
if problem_v_kuchyni and zadna_voda_zvenku then
unik_vody_v_kuchyni.
Interpret pro novou syntaxi
is_true(P):- ask(P).
is_true(ne P):- not ask(P).
is_true(P):- if Podmínka then P, is_true(Podmínka).
is_true(P1 and P2):- is_true(P1),is_true(P2).
is_true(P1 or P2):- is_true(P1);is_true(P2).
Příklad dotazu:
?- is_true(unik_vody_v_koupelne).
Procedura ask musí být upravena následovně:
ask(X):- known(ano,X),!.
ask(X):- known(_,X),!,fail.
ask(X):- atomic(X), not if Podminka then X,
write(X),write('?(ano/ne): '),
read(Y),asserta(known(Y,X)),Y=ano.
Pro opětovné použití expertního systému je nutné doplnit predikát reset:
reset:- retractall(known(_,_)).
- 75 -
Jiří Dvořák
Jazyky pro umělou inteligenci
Mechanismus dopředného řetězení
forward:- new_derived_fact(P),!,
write('Odvozeno:'),write(P),nl,
assert(known(ano,P)),forward;
write('Zadna dalsi fakta:').
new_derived_fact(Zaver):- if Podm then Zaver,
not known(ano,Zaver),composed_fact(Podm).
composed_fact(Podm):- ask(Podm).
composed_fact(ne Podm):- not ask(Podm).
composed_fact(Podm1 and Podm2):composed_fact(Podm1),composed_fact(Podm2).
composed_fact(Podm1 or Podm2):composed_fact(Podm1);composed_fact(Podm2).
Vysvětlovací mechanismus
:-op(800,xfx,<=).
explain(P):- known(ano,P), write(known(ano,P)), nl.
explain(ne P):- known(ne,P), write(known(ne,P)), nl.
explain(P):- if Podm then P,
write(P <= Podm),nl,explain(Podm).
explain(P1 and P2):- explain(P1),explain(P2).
explain(P1 or P2):- explain(P1);explain(P2).
Predikát explain poskytne vysvětlení, jak systém při vyhodnocení dotazu is_true(P) dospěl
k závěru, že P platí. Neúspěch při vyhodnocení tohoto dotazu vysvětlí následující predikát
why_fails.
why_fails(P):- known(ne,P), write(known(ne,P)), nl.
why_fails(ne P):- known(ano,P), write(known(ano,P)), nl.
why_fails(P):- if Podm then P,
write(P <= Podm),nl,why_fails(Podm).
why_fails(P1 and P2):- why_fails(P1);why_fails(P2).
why_fails(P1 or P2):- why_fails(P1),why_fails(P2).
Příklady dotazů:
?- explain(unik_vody_v_koupelne).
?- why_fails(unik_vody_v_koupelne).
- 76 -
Jiří Dvořák
Jazyky pro umělou inteligenci
Zpracování neurčitosti
:-op(900,xfx,::).
% Operator pro zadani jistoty pravidla
certainty(P,Jist):-ask(P,Jist).
certainty(ne P,Jist):-ask(P,J),Jist is 1-J.
certainty(P,Jist):if Podm then P :: J1,
certainty(Podm,J2),
Jist is J1*J2.
certainty(Podm1 and Podm2,Jist):certainty(Podm1,Jist1),
certainty(Podm2,Jist2),
Jist is min(Jist1,Jist2).
certainty(Podm1 or Podm2,Jist):certainty(Podm1,Jist1),
certainty(Podm2,Jist2),
Jist is max(Jist1,Jist2).
% Interface
ask(X,J):-known(X,J),!.
ask(X,J):-atomic(X), not if Podminka then X :: _,
write(X),write(' ?jistota: '),
read(J),asserta(known(X,J)).
Předpokládáme, že stupně jistoty pravidel a faktů nabývají hodnot z intervalu 〈0; 1〉.
Příklad pravidel:
if hala_mokra and kuchyn_sucha then
unik_vody_v_koupelne :: 0.8.
if hala_mokra and koupelna_sucha then
problem_v_kuchyni :: 0.9.
if not dest or okno_zavrene then zadna_voda_zvenku :: 1.
if problem_v_kuchyni and zadna_voda_zvenku then
unik_vody _v_kuchyni :: 1.
Příklad dotazu:
?- certainty(unik_vody_v_koupelne,X).
- 77 -
Jiří Dvořák
Jazyky pro umělou inteligenci
Literatura
[1]
Bratko, I. Prolog Programming for Artificial Intelligence. Wokingham, England, AddisonWesley 1990.
[2]
Bahrami, A. Designing Artificial Intelligence Based Software. Wilmslow, Sigma Press
1988.
[3]
Callear, D. Prolog Programming for Students. London, Thomson 1994.
[4]
Charniak, E. et al. Artificial Intelligence Programming. Hillsdale, New Jersey, LEA
Publishers 1987.
[5]
Clocksin, W.F. and Mellish, C.S. Programming in Prolog. Berlin, Springer-Verlag 1996.
[6]
Deransart, P., Ed-Dbali, A. Cervoni, L. Prolog: The Standard. Berlin, Springer-Verlag
1996.
[7]
Eisenbach, S. (ed.). Functional Programming: Languages, Tools and Architectures.
Chichester, England, Ellis Horwood Limited 1987.
[8]
Grillmeyer, O. Exploring Computer Science with Scheme. Berlin, Springer-Verlag 1998.
[9]
Jirků, P. a kol. Programování v jazyku Prolog. Praha, SNTL 1991.
[10] Kalaš, I. Iné programovanie – stretnutie s jazykom Lisp. Bratislava, Alfa 1990.
[11] Kolář, J. Jazyky pro umělou inteligenci. Skripta. Praha, ČVUT 1994.
[12] Koschmann, T.D. The Common Lisp Companion. New York, John Wiley & Sons 1990.
[13] Luger, G.F. Artificial Intelligence. Structures and strategies for Complex Problem Solving.
Harlow, Addison-Wesley 2002.
[14] Mařík, V. a kol. Umělá inteligence 2. Praha, Academia 1997.
[15] Merrit, D. Building Expert Systems in Prolog. Berlin, Springer-Verlag 1989.
[16] Molnár, L. a Návrat, P. Programovanie v jazyku Lisp. Bratislava, Alfa 1988.
[17] Polák, J. Prolog. Praha, Grada 1992.
[18] Queinnec, Ch. Lisp in Small Pieces. Cambridge University Press 1996.
[19] Sterling, L. and Shapiro, E. The Art of Prolog. Cambridge, Massachusets, MIT Press 1994.
[20] Spivey, M. An Introduction to Logic Programming through Prolog. London, Prentice Hall
1996.
[21] Thomson, S. Miranda: The Craft of Functional Programming. Wokingham, England,
Addison-Wesley 1995.
[22] Wilensky, R. LISPcraft. New York, Norton, 1984.
[23] Winstanley, G. Program Design for Knowledge Based Systems. Wilmslow, Sigma Press
1987.
[24] Winston, P.H. and Horn, B.K.P. Lisp. Reading, Massachusets, Addison-Wesley 1981.
- 78 -
Jiří Dvořák
Jazyky pro umělou inteligenci
Seznam internetových adres
Funkcionální programovací jazyky
Franz Inc_Home Page http://www.franz.com/
BBTech Corporation http://www.bbtech.com/
Haskell http://www.haskell.org/
Miranda http://www.engin.umd.umich.edu/CIS/course.des/cis400/miranda/miranda.html
New York University Natural Language Computing Project Downloading Free Software
http://dce.felk.cvut.cz/lisp/nclp/free.html
NYU Natural Language Computing -- LISP Tutorial http://dce.felk.cvut.cz/lisp/nclp/lisp.html
Scheme http://swiss.csail.mit.edu/projects/scheme/
Xanalys LispWorks http://www.lispworks.com/
XLISP Home Page http://www.mv.com/ipusers/xlisper/
XLISP-PLUS Home Page http://almy.us/xlisp.html
Logické programovací jazyky
Amzi!® http://www.amzi.com/
Amzi! Logic Explorer http://www.amzi.com/share.htm
Applied Logic Systems, Inc. http://www.als.com/als.html
Cetus Links OO Prolog http://www.cetus-links.org/oo_prolog.html
ECLiPSe http://www.icparc.ic.ac.uk/eclipse/
Gödel http://www.cs.bris.ac.uk/~bowers/goedel.html
Interactive Prolog Guide http://kti.ms.mff.cuni.cz/~bartak/prolog/
Logic Programming Associates http://www.lpa.co.uk/
LPA Prolog http://www.lpa.co.uk/
Mercury http://www.cs.mu.oz.au/research/mercury/
Merritt, D. Building Expert Systems in Prolog
http://www.amzi.com/ExpertSystemsInProlog/index.htm
Nilsson, U. and Maluszynski, J. Logic, Programming and Prolog http://www.ida.liu.se/~ulfni/lpp/
Quintus Prolog http://www.quintus.com/products/prolog/
Read Adventure in Prolog on-line http://www.amzi.com/AdventureInProlog/index.htm
SICStus Prolog 3 http://www.sics.se/sicstus.html
Strawberry Prolog http://www.dobrev.com/
SWI-Prolog http://www.swi-prolog.org/
Visual Prolog http://www.visual-prolog.com/
- 79 -
Jiří Dvořák
Jazyky pro umělou inteligenci
Příloha A: Přehled definovaných jmen Lispu
and
22
mapl
29
append
19
maplist
28
apply
24
member
20
atom
15
minusp
22
boundp
21
nconc
31
car
13
not
22
cdr
13
null
20
close
35
numberp 21
cond
15
open
35
cons
14
or
22
defmacro
30
plusp
22
defun
16
princ
35
do
25
print
35
dolist
26
prog1
27
dotimes
26
prog2
27
eq
15
progn
27
equal
20
progv
27
eval
23
putprop
34
funcall
24
quote
12
gensym
34
read
35
get
34
remprop
34
last
19
reverse
20
length
19
rplaca
31
let
27
rplacd
31
lambda
29
setf
32
list
19
setq
13
listp
21
symbolp
21
mapc
28
terpri
35
mapcan
32
zerop
22
mapcar
28
mapcon
32
- 80 -
Jiří Dvořák
Jazyky pro umělou inteligenci
Příloha B: Přehled definovaných jmen a operátorů Prologu
=
51
is
57
==
66
nl
71
=..
64
nonvar
65
\=
51
not
68
\==
66
op
61
arg
64
put
71
assert
68
read
71
atom
63
reconsult
68
atomic
63
repeat
72
bagof
69
retract
68
call
66
see
70
clause
69
seeing
70
compound
63
seen
70
constant
63
setof
69
consult
68
tab
71
fail
68
tell
70
findall
70
telling
70
functor
64
told
70
get
71
var
65
get0
71
write
71
integer
63
- 81 -
Jiří Dvořák
Jazyky pro umělou inteligenci
Příloha C: Výsledky úloh na procvičení
Úlohy 2.5
a)
(defun Abs (X)
(cond ((< X 0) (- 0 X))
(T X)))
b)
(defun Max (X Y)
(cond ((>= X Y) X)
(T Y) ))
c)
(defun Delka (S)
(cond ((Prazdny S) 0)
(T (+ 1 (Delka (cdr S)))) ))
d)
(defun Posl (X)
(cond ((Prazdny X) nil)
((Prazdny (cdr X)) (car X))
(T (Posl(cdr X))) ))
e)
(defun BezPosl (X)
(cond ((Prazdny X) nil)
((Prazdny (cdr X)) nil)
(T (cons (car X) (BezPosl (cdr X)))) ))
Úlohy 2.7
a)
(defun Clen (X S)
(cond ((null S) nil)
((equal X (car S)) S)
(T (Clen X) (cdr S))) ))
- 82 -
Jiří Dvořák
Jazyky pro umělou inteligenci
Úlohy 2.9
a)
(defun fact (n) (fact_kr n 1))
(defun fact_kr (n acc)
(cond ((zerop n) acc)
(t (fact_kr (- n 1) (* n acc))) ))
b)
(defun fakt (n)
(do ((i n (- i 1))
(m 1 (* m i)))
((zerop i) m)))
c)
(defun sum-list (num-list)
(do ((x num-list (cdr x))
(s 0 (+ s (car x))))
((null x) s)))
;Jina verze
(defun sum1-list (num-list)
(setq s 0)
(do ((x num-list (cdr x)))
((null x) s)
(setq s (+ s (car x)))))
Úlohy 4.7
a)
% Z je maximem z cisel X a Y
max(X,Y,Z):-Z>=X,Z>=Y,Z=:=X.
max(X,Y,Z):-Z>=X,Z>=Y,Z=:=Y.
% Jednodussi formulace
max(X,Y,X):-X>=Y.
max(X,Y,Y):-X<Y.
b)
gcd(N,N,N).
gcd(M,N,D):-M>N,M1 is M-N, gcd(M1,N,D).
- 83 -
Jiří Dvořák
Jazyky pro umělou inteligenci
gcd(M,N,D):-M<N,gcd(N,M,D).
Úlohy 4.8
a)
pridej(X,L,L):-clen(X,L).
pridej(X,L,[X|L]).
b)
pridej_na_konec([],X,[X]).
pridej_na_konec([H|T],X,[H|NT]):- pridej_na_konec(T,X,NT).
c)
zrus(X,[X|L],L).
zrus(X,[Zac|Zb],[Zac|L]):-zrus(X,Zb,L).
d)
posl([X],X).
posl([X,Y|T],Z):-posl([Y|T],Z).
e)
bezposl([],[]).
bezposl([H|T],[H|NT]):-bezposl(T,NT).
f)
posl(L,X):-pridej_na_konec(_,X,L).
bezposl(L,NL):-pridej_na_konec(NL,_,L).
g)
delka([],0).
delka([X|Y],N):- delka(Y,N1), N is N1+1.
- 84 -

Podobné dokumenty

peugeot exalt

peugeot exalt přesv2dčen Vincent Portia, produktový š0f tohoto modelu.

Více

NEPROCEDURÁLNÍ PROGRAMOVÁNÍ

NEPROCEDURÁLNÍ PROGRAMOVÁNÍ který chceme řešit a n e n u t n ě p ř e s n ý z p ů s o b , j a k ý m s e m á ř e š i t . Historicky nacházejí takové jazyky uplatnění především v oblasti tzv. umělé inteligence, která je mimo jin...

Více

Kapitoly z dějin informatiky 3 Vývoj software ve 20. století

Kapitoly z dějin informatiky 3 Vývoj software ve 20. století hardware a že je potřeba vymyslet novou „technologii“ programování, která by využití počítačů nebrzdila. Prvním krokem od strojového kódu k symbolickému jazyku přístupnému člověku byla skupina jazy...

Více

LISP - Funkcionály

LISP - Funkcionály LISTP testují typ dat, =, EQ, EQUAL, testují rovnost, <, >, … testují pořadí SETQ, SET přiřazují symbolům globální hodnoty DEFUN definuje funkci, parametry jsou v ní lokální. COND umožňuje výběr al...

Více

PROGRAMOVÁNÍ V SQL

PROGRAMOVÁNÍ V SQL Jazyk SQL (Structured Query Language - strukturovaný dotazovací jazyk) je v případě SQL databázových serverů rozhraním, které slouží ke zpřístupnění dat. Jazyk SQL byl vyvinut firmou IBM na počátku...

Více

1 Expertní systémy

1 Expertní systémy označujeme jednotlivé hypotézy a z obrázku je patrné, že všechny hypotézy lze rozdělit do tří skupin. První skupinu tvoří takové hypotézy, které nejsou odvoditelné. V našem příklady jsou to hypotéz...

Více

volitelné parametry jsou nastaveny na nil, pokud nejsou při

volitelné parametry jsou nastaveny na nil, pokud nejsou při Řídící strategie v Lispu: rekurze x mapování x klasická iterace - všeobecně píšeme především čitelný kód - při programování matematických funkcí se držíme strategie použité při matematické definici...

Více

aplikuje funkci na stejnolehlé prvky seznamů a vr

aplikuje funkci na stejnolehlé prvky seznamů a vr příklad: (suma-list '(1 2 3) '(4 5 6) '(7 8 9)) -> (12 15 18) Anonymní funkce - Lambda

Více