Lispkurs - Zum Praktikum „KI-basierte Robotersteuerung“

Werbung
Lispkurs
”
Zum Praktikum
KI-basierte Robotersteuerung“
Lorenz Mösenlechner
[email protected]
Thomas Rühr
[email protected]
Organisatorisches
Arbeit in Gruppen bis zu drei Leuten
Aufbau des Praktikums
1. Teil: KI-Aufgaben
2. Teil: Projektaufgabe
Überblick
1. Stunde: Grundlegende Lisp-Programmierung
Einführung
Interpretation von Lisp-Ausdrücken
Funktionale Programmierung
2. Stunde: Weiterführende Lisp-Konstrukte
Imperative Programmierung
Sonstiges
Teil I
Grundlegende Lisp-Programmierung
Überblick
1. Einführung
2. Interpretation von Lisp-Ausdrücken
3. Funktionale Programmierung
Warum Lisp?
Wichtigste Sprache im Bereich der KI
Effizienz
Schnelle Programmentwicklung möglich
Kurze, gut lesbare Programme
Warum nicht C?
Verschiedene Programmierstile
(funktional, imperativ, objektorientiert)
Automatische Speicherverwaltung
Dynamische Typisierung
Eingebaute Listenverarbeitung
Funktionen höherer Ordnung
Gleichförmige Syntax
Lisp kann Code zur Laufzeit erzeugen
Erweiterbarkeit
Kurze Geschichte von Lisp
1958: John McCarthy beginnt Arbeit an Lisp 1
Lisp 1.5 bis Ende der 60er Jahre
Ende der 60er Jahre:
MacLisp (Effizienz), InterLisp (Bedienbarkeit)
1975: Entwicklung von Scheme
1980: Existenz einer Vielzahl von Lisp-Dialekten, die meist
untereinander inkompatibel waren
1984: Guy Steele Jr: Common Lisp the Language
aktuell: ANSI-Standard
Was Lisp alles kann
Editoren: Multics Emacs, Gnu Emacs
KI: SHRDLU, Eliza, Student, MYCIN (eines der ersten
Expertensysteme), Macsyma
CAD: AutoCAD
Mirai (Izware): Lisp-basiertes Animationssystem (benutzt um
Gollum und andere Figuren in The Lord of the Rings“ zu
”
erschaffen)
Jak and Daxter: the Precursor Legacy (Naughty Dog
Software): 3D Spiel für Sony Playstation II
Wie sieht Lisp aus?
Arithmetik:
Listen:
Funktionen:
(+ 1 2 3 4 5 6 7 8 9)
(/ 2 4)
(∗ (+ 7 3) (- 15 27))
nil =()
b =’()
b
(cons 3 ’(2 1))
(first ’(1 2 3 4))
(rest ’(1 2 3 4))
(defun fakultaet (n)
(∗ n (fakultaet (- n 1))))
(fakultaet 5)
Was heißt eigentlich LISP“?
”
steht offiziell für
LISt Processing language“
”
Was heißt eigentlich LISP“?
”
steht offiziell für
LISt Processing language“
”
böse Zungen behaupten, es ist die Abkürzung für
Lots of Irritating Superfluous Parentheses“
”
Überblick
1. Einführung
2. Interpretation von Lisp-Ausdrücken
Atome
Listen
Daten und Code
3. Funktionale Programmierung
Die Read-Eval-Print-Schleife
read
"hören"
eval
"verstehen"
print
"antworten"
Atome
Nicht-Symbole (Konstanten)
Zahlen
Zeichen (Characters)
Zeichenreihen (Strings)
Symbole
Variablen
Alles, was nicht anders interpretiert werden kann, ist ein
Symbol.
Groß- und Kleinschreibung spielt keine Rolle.
Auswertung von Atomen
Nicht-Symbole: werden zu sich selbst ausgewertet
Symbole: zugewiesener Wert
Beispiel
x
⇒ Error: Attempt to take the value of the unbound variable ‘X’.
(setf x 4)
⇒4
x
⇒4
Listen
Form: (a b c d)
Leere Liste: ’() bzw. nil
Interne Struktur von Listen:
cons-Zelle: Paar von Zeigern
der erste heißt car
der zweite heißt cdr
Schreibweise als dotted pair“: ’(1 . 2)
”
Eine Liste wird intern als cons-Zellen dargestellt.
’(1 (2 3) 4)
(cons 1 (cons (cons 2 (cons 3 nil)) (cons 4 nil)))
’(1 . ((2 . (3 . nil)) . (4 . nil)))
Auswertung von Listen
Das erste Listenelement kann sein:
Funktionsname
1. Werte alle Argumente (d.h. Rest der Liste) aus
2. Wende Funktion auf ausgewertete Argumente an
Spezialform: Auswertung je nach Funktionalität
Makro
1. Expandiere Makro
2. Auswertung der Expansion
Verhinderung der Auswertung
Mit der Spezialform quote kann man die Auswertung eines
Ausdrucks verhindern.
Abkürzung: (quote x) entspricht ’x
Aufpassen bei Listen, die Daten enthalten!
Beispiel
x ⇒ Error:...
(quote x) ⇒ X
(1 2 3)
⇒ Error: Funcall of 1 which is a non-function.
’(1 2 3)
⇒ (1 2 3)
Zusammenhang zwischen Daten und Code
Lisp hat nur die grundlegenden Konstrukte
Atom“ und Liste“
”
”
gequotete Konstrukte werden als Daten verstanden
alles andere ist Code
Fazit
äquivalente Darstellung von Daten und Code
dadurch einfacher Übergang von Daten zu Code
insbesondere Codegenerierung zur Laufzeit möglich!
Zusammenfassung
read
Symbol
Wert des Symbols (der Variable)
Nicht−Symbol
eigener Wert
special form
entsprechende Auswertung der Argumente
Makro
1. Funktionsaufruf von macroexpand
2. Funktionsaufruf von eval
Funktion
1. eval auf dem Rest der Liste
2. Funktionsanwendung auf ausgewertete Argumente
Atom
"hören"
Ausdruck
eval
"verstehen"
Liste
print
"antworten"
Überblick
1. Einführung
2. Interpretation von Lisp-Ausdrücken
3. Funktionale Programmierung
Arithmetik
Listen
Vergleiche
Funktionen
Lokale Variablenbindung
Bedinungen
Logische Operatoren
Arithmetische Operationen
Rechnen: + - ∗ /
Vergleichen: <
Zahlen finden:
>
<= >=
=
(random 100): Zufallszahl zwischen 0 und 99
(min 2 3 4): Minimum bestimmen (genauso max)
Noch mehr Rechnen:
(1+ x) =(+
b
x 1), (1- x) =(b x 1) √
(expt x y): x y , (exp x): e x , (sqrt x): x
(round x), (truncate x), (floor x), (ceiling x)
(sin x), (cos x), (tan x), (asin x), (acos x), (atan x)
Listenoperationen (1)
Konstruktion
(cons a l) fügt Element a vorne an Liste l an
(list a1 a2 ...) konstruiert eine Liste mit den Elementen a1, a2,
...
(append l1 l2) konkateniert Listen l1 und l2
Prädikate
(null l) Ist l die leere Liste?
(listp x) Ist x eine Liste?
(member a l) Ist a ein Element der Liste l?
Listenoperationen (2)
Selektion
(first l) erstes Listenelement von l
ältere Bezeichnung: (car l)
(second l), (third l), ... (tenth l)
zweites/drittes/...zehntes Listenelement
(nth i l) i-tes Element von l
(last l) Liste mit letztem Element von l
(rest l) Liste l ohne das erste Element
ältere Bezeichnung: (cdr l)
Listenoperationen (3)
Assoziationslisten
Eine Assoziationsliste ist eine Liste von Dotted Pairs“.
”
Jedes Paar besteht aus einem Schlüssel und einem Wert.
assoc sucht nach einem bestimmten Schlüssel:
(assoc 2 ’((1 . a) (2 . b) (3 . c))) ⇒ (2 . B)
rassoc sucht nach einem bestimmten Wert:
(rassoc ’b ’((1 . a) (2 . b) (3 . c))) ⇒ (2 . B)
Gleichheitsoperationen (1)
eq vergleicht Symbole (also Zeiger)
eql vergleicht Symbole und Zahlen
equal vergleicht Ausdrücke jeden Typs
equalp ist noch allgemeiner
Strings mit verschiedener Groß- und Kleinschreibung
Zahlen verschiedenen Typs
= vergleicht Zahlen
string= vergleicht Zeichenreihen
Gleichheitsoperationen (2)
x
’x
0
’(x)
”xy”
”Xy”
0
0
y
’x
0
’(x)
”xy”
”xY”
0.0
1
eq
T
?
nil
nil
nil
nil
nil
eql
T
T
nil
nil
nil
nil
nil
equal
T
T
T
T
nil
nil
nil
equalp
T
T
T
T
T
T
nil
eq
eql
equal
equalp
Funktionen
Definition: (defun NAME PARAMLIST BODY)
Besonderheiten bei der Parameterliste
Beliebig viele Parameter:
(par-1 ... par-n &rest rest)
Optionale Parameter:
(par-1 ... par-n &optional opt-1 ... opt-m)
Schlüsselwort-Argumente:
(par-1 ... par-n &key opt-1 ... opt-m)
opt-i kann folgende Gestalt haben:
name
(name wert)
(name wert sp)
Funktionen - Beispiele (1)
rest-Parameter
(defun plus (a &rest l)
(+ a (sum-up l)))
(defun sum-up (l)
(if (null l)
0
(+ (first l) (sum-up (rest l)))))
(plus 1 2 3) ⇒ 6
Funktionen - Beispiele (2)
key-Parameter
(defun bsp (a b &key c (d a))
(list a b c d))
(bsp 1 2)
⇒ (1 2 nil 1)
(bsp 1 2 :d 3 :c 4) ⇒ (1 2 4 3)
Funktionen als Werte
Funktionen können an andere Funktionen übergeben werden.
Funktionen können als Resultat zurückgegeben werden.
Funktionen, deren Argumente oder Resultate Funktionen sind,
nennt man Funktionen höherer Ordnung.
Werte eines Symbols
Ein Symbol kann gleichzeitig
einen Variablenwert,
einen funktionalen Wert und
eine Property-List
haben.
Den funktionalen Wert erhält man
mit der Spezialform function
mit #’ als Abkürzung für function
mit symbol-function
Werte eines Symbols — Beispiele
Funktionswert eines Symbols
(function cons) =#’cons
b
=(symbol-function
b
’cons)
⇒ #<Function CONS>
Beschreibung eines Symbols
(defun x () 3)
(setf x 5)
(describe ’x)
⇒
X is a SYMBOL.
Its value is 5
It is INTERNAL in the COMMON-LISP-USER package.
Its function binding is #<Interpreted Function X>
The function takes arguments ()
Funktionen höherer Ordnung (1)
Mit funcall und apply kann man funktionale Werte auf ihre
Argumente anwenden
Diese Ausdrücke sind äquivalent
(cons 1 2)
(funcall #’cons 1 2)
(apply #’cons ’(1 2))
Statt apply und funcall kann man auch eval benutzen, was
allerdings kein guter Stil ist:
Beispiel
(eval ’(cons 1 2))
Funktionen höherer Ordnung (2)
Statt Schleifen verwendet man in Lisp gern die Funktion
mapcar. Sie wendet eine Funktion auf alle Elemente einer
(mehrerer) Liste(n) an.
Beispiele:
Beispiel
(mapcar #’1- ’(1 2 3))
⇒ (0 1 2)
(mapcar #’first ’((a b) (c d))) ⇒ (A C)
(mapcar #’+ ’(1 2 3) ’(4 5 6)) ⇒ (5 7 9)
Nochmal Symbole (1)
Mit intern kann man aus einer Zeichenkette ein Symbol
machen.
Beispiel zu function und symbol-function:
Beispiel
(function (intern ”CONS”))
⇒ Error: ‘(INTERN ”CONS”)’ is not fbound
(symbol-function (intern ”CONS”))
⇒ #<Function CONS>
Nochmal Symbole (2)
Warum will man Symbole erzeugen?
Früher
(cond ((equal x a) (fun-a))
((equal x b) (fun-b))
((equal x c) (fun-c)))
Jetzt
(funcall
(symbol-function
(intern (string-upcase (concatenate ’string ”fun-” x)))))
Anonyme Funktionen
Zu jedem Element einer Liste 5 addieren
(defun plus5 (x) (+ x 5))
(mapcar #’plus5 liste)
Dies ist allerdings die einzige Stelle in unserem Programm, wo
plus5 benötigt wird. Daher wäre es besser, wenn wir der Funktion
gar keinen Namen geben müssten.
Mit anonymer Funktion
(mapcar #’(lambda (x) (+ x 5)) liste)
Allgemeiner Aufbau einer Anonymen Funktion:
(lambda PARAMLIST BODY)
Variablenbindung mit let
let bildet einen Programmblock mit lokalen Variablen:
Beispiel
(let ( (x 40)
(y (+ 1 1)) )
(+ x y))
⇒ 42
Vergleich mit Lambda-Abstraktion:
(let ((x val)) body) und
((lambda (x) body) val)
sind äquivalent.
Variablenbindung mit let*
Bei let ist die Reihenfolge der Variablenbindung nicht
spezifiziert.
Bei let* werden die Variablen der Reihe nach gebunden.
Beispiel
(let ( (x 1)
(y (∗ 2 x)) )
(+ x y))
⇒ Error: Attempt to take the value of the unbound variable ‘X’.
(let* ( (x 1)
(y (∗ 2 x)) )
(+ x y))
⇒3
Bindung von Funktionen (1)
Mit flet und labels kann man lokal Funktionen definieren.
Der Wirkungsbereich von flet ist nur der Rumpf des
flet-Ausdrucks.
Der Wirkungsbereich von labels ist der Rumpf und die
labels-Deklaration selbst. Die mit labels deklarierten
Funktionen können sich also gegenseitig aufrufen.
Bindung von Funktionen (2)
Beispiel
(defun tail-length (liste)
(labels ( (tail-length-1 (l acc)
(if (null l)
acc
(tail-length-1 (rest l) (1+ acc)))) )
(tail-length-1 liste 0)))
Bedingungen (1)
Einfache Fallunterscheidung: (if p a b)
Wenn p erfüllt ist, mache a, sonst mache b.
Nur ein zu betrachtender Fall:
when: (when p a-1 ... a-n)
Wenn p erfüllt ist, führe a-1 bis a-n aus.
unless: (unless p a-1 ... a-n)
Wenn p nicht erfüllt ist, führe a-1 bis a-n aus.
Bedingungen (2)
Komplexere Fallunterscheidung:
(cond (p1 a1)
(p2 a2)
...
(pn an))
Logische Operatoren
Der Wahrheitswert false wird durch nil ausgedrückt.
Alles andere ist true, insbesondere die Konstante t.
Logische Operatoren: not, and, or
Besonderheiten bei and und or
lazy evaluation“
”
beliebig viele Argumente möglich:
Beispiel
(and (or nil 3 2 (and (not 4) 1 2 3)) t (not nil) (and 7 5 3 1))
interne Darstellung durch if und cond
Teil II
Weiterführende Lisp-Konstrukte
Überblick
4. Imperative Programmierung
Wertzuweisung und Seiteneffekte
Variablendeklaration
Sequentielle Komposition
Schleifen
Ein-/Ausgabe
5. Datentypen
6. Was es sonst noch gibt
Wertzuweisung
Spezialform setf
Beispiel
(setf var-1 expr-1 ... var-n expr-n)
var-i bekommt den Wert expr-i zugewiesen
Abarbeitung von links nach rechts
Beispiel
(setf x 10 y 20 z (+ x y))
Andere Möglichkeiten der Wertzuweisung: set, setq
Seiteneffekte
Veränderung beliebiger Strukturelemente
Beispiel
(setf liste ’(1 2 3 4))
(setf (second liste) 5)
liste
⇒ (1 5 3 4)
Funktionen, die setf enthalten:
(push a l) =(setf
b
l (cons a l))
(pop l) =(prog1
b
(first l)
(setf l (rest l))
(incf a) =(setf
b
a (1+ a))
(decf a) =(setf
b
a (1- a))
Variablendeklaration
Globale Variablen:
(defvar var-name init-val)
init-val ist optional
(defparameter var-name value)
(defconstant var-name value)
value muss angegeben werden und kann im Programm nicht
verändert werden
Oder let-Ausdruck mit globalen Variablen,
Funktionsdefinitionen im Rumpf.
Am besten keine globalen Variablen verwenden.
Sequentielle Komposition
Aneinanderreihung mehrerer Kommandos
(prog1 form-1 ... form-n)
form-1 bis form-n werden ausgewertet
Ergebnis des Ausdrucks ist Resultat von form-1
(progn form-1 ... form-n)
Ergebnis des Ausdrucks ist Resultat von form-n
In vielen Befehlen implizite progn-Anweisung:
defun, let, cond, when, unless
Achtung bei if !
Hier gibt es keine implizite progn-Anweisung.
Äquivalent zu finally:
(unwind-protect <body> <protection-forms>)
Schleifen
Allgemeinste Schleifen: do und loop
Weitere häufig benötigte Schleifen:
(dolist (var list res) body)
(dotimes (var upper res) body)
Beispiel
(let ((x 0))
(dolist (v ’(1 2 3) (* 3 x))
(setf x (+ x v))))
⇒ 18
Schleifen sollten nach Möglichkeit vermieden und durch
Rekursion oder Mapper ersetzt werden!
Ein-/Ausgabe
Eingabe:
(read) gibt eingelesenen Ausdruck zurück
(read-line) gibt eingelesene Zeile als String zurück
Ausgabe:
(format stream ”format-string” arg-1 ... arg-n)
Ausgabe mit format
Stream-Parameter:
Rückgabe des Format-Strings: nil
Ausgabe des Format-Strings: T
Formatierungsanweisungen
Alles mögliche: ∼a (Ascii)
Formatierte Zahlen: ∼f
Zeilenumbruch: ∼%
Beispiele
(format
(format
(format
(format
nil ”hello, world”)
t ”hello, world”)
t ”Irgendwas: ∼a, ∼a, ∼a” 25 ”hallo” ’(1 2))
t ”Zahl mit 2 Nachkommastellen: ∼,2f” 0.1234)
Ein-/Ausgabe von Dateien
Öffnen einer Datei: (open file)
Schließen eines Streams: (close stream)
Einfacher mit with-open-file
(with-open-file (stream file) body)
ist äquivalent zu
(let ((stream (open file)))
(unwind-protect
body
(close stream)))
Optionen für Ausgabe zum Überschreiben der Datei
(with-open-file (s f :direction :output :if-exists :supersede) body)
Überblick
4. Imperative Programmierung
5. Datentypen
6. Was es sonst noch gibt
Datentypen
Lisp ist dynamisch typisiert, d.h. Typinformation wird zur
Laufzeit überprüft
Auszug aus der Typhierarchie:
character
function
rational
real
t
number
symbol
integer
ratio
float
complex
string
vector
sequence
bit−vector
cons
list
null
fixnum
bignum
Typprüfung zur Laufzeit
Funktionen für die Typprüfung:
Allgemein: (typep obj typ)
Typspezifisch
Typerkennung: (type-of obj)
Beispiele
(typep 5 ’number) ⇒ T
(type-of #c(5 1)) ⇒ COMPLEX
(type-of ’nil)
⇒ NULL
(listp ’(1 2 3))
(numberp 23)
(symbolp ’a)
(ratiop 2/3)
⇒
⇒
⇒
⇒
T
T
T
T
Strukturen
defstruct Typconstruktor zum Generieren von
Record/Struktur-Typen
Beispiel
(defstruct cons-cell
car
cdr)
Generierung neuer Funktionen:
Konstruktor: make-cons-cell
Selektor: cons-cell-car, cons-cell-cdr
Typerkenner: cons-cell-p
Kopierfunktion: copy-cons-cell
Hash-tables
Common lisp unterstützt hash-tables als built-in Datentyp
Erstellen einer hash-table: make-hash-table
Look-up: get-hash
Ändern eines Eintrags:
(setf (gethash key hash-table) new-value)
Löschen eines Eintrags: remhash
Überblick
4. Imperative Programmierung
5. Datentypen
6. Was es sonst noch gibt
Makros
Mehrere Rückgabewerte
Debugging
Makros
Eigene Sprachkonstrukte erzeugen
Definition ähnlich wie bei Funktionen mit defmacro
Viele eingebaute Lisp-Makros: and, or, cond, defun
Auswertung von Makros:
1. Aufruf von macroexpand
2. Evaluierung der erhaltenen Lisp-Form
Beispiel:
Beispiel
(macroexpand ’(and a b))
⇒ (IF (NOT A) (PROGN NIL) (COND (T B)))
Mehrere Rückgabewerte
Funktionen hatten bisher einen Rückgabewert.
Für mehrere Werte hat man zwei Möglichkeiten
1. (list a-1 ... a-n) ⇒ ein Rückgabewert
2. (values a-1 ... a-n) ⇒ mehrere Rückgabewerte
Ansprechen der einzelnen Rückgabewerte:
1. mit first, second, ...
2. Bindung der Rückgabewerte
multiple-value-list
muliple-value-bind
Beispiel
(multiple-value-bind (w r) (floor 13 12) (+ w r)) ⇒ 2
Debugging
Verfolgen rekursiver Funktionen: (trace hfunctioni)
Ausschalten: (untrace hfunctioni)
Ausgaben mit (format t ...)
kleine Funktionen/Funktionsteile direkt in der Lisp-Umgebung
entwickeln
Außerdem nützlich:
(describe ’hsymboli)
(apropos ’hsymboli)
liefert alle bekannten Symbole mit hsymboli im Namen
Zugehörige Unterlagen
Herunterladen