Grundlagen der funktionsorientierten Programmierung mit SCHEME/Der Lambda-Kalkül
Aus ProgrammingWiki
Wiki-Quelltext dieser Seite.
<language>Scheme</language>
== Einführung ==
[[Datei:Church.jpg|miniatur|Alonzo Church]]
Der λ-Kalkül fasst berechenbare Funktionen zu einer abstrakten Klasse zusammen.<br /><br />
Er wurde um 1936 durch '''Alonzo Church''' (* 14. Juni 1903; † 11. August 1995; US-amerikanischer Mathematiker, Logiker und Philosoph; Mitbegründer der theoretischen Informatik) entwickelt und bildet die theoretische Grundlage funktionaler bzw. funktionsorientierter Programmiersprachen.<br />
'''Hinweis:'''<br />
Der λ-Kalkül und die [http://de.wikipedia.org/wiki/Turingmaschine Turingmaschine] (theoretisches Fundament imperativer Programmiersprachen) beschreiben äquivalente Klassen berechenbarer Funktionen.
Damit sind funktionale bzw. funktionsorientierte Programmiersprachen (z.B. Haskell, Scheme, Logo) und imperative Programmiersprachen (z.B. Visual Basic, Delphi, C++) "gleich mächtig".<br /><br />
Die ''Grundidee'' des λ-Kalküls besteht darin, die Klasse der ''λ-Ausdrücke'' zu bilden,
* die aus gegebenen Grundbausteinen aufbaut werden und
* für die bestimmte Rechenregeln bestehen.<br /><br />
== Definition der λ-Ausdrücke ==
<code id="4b65bb7a9fe6c" hidden>
(define pi (* 4 (atan 1)))
(define sqr
(lambda (x)
(* x x)))
</code>
<ol>
<li>
Der Vorrat an Grundbausteinen ist eine (abzählbar) unendliche Menge von Variablen
<math>\{x_1, x_2, x_3, ...\}</math>, die selbst λ-Ausdrücke sind.<br />
'''Beispiele:'''
<table>
<tr>
<td><run id="4b656369f02d3">pi</run></td>
<td><run id="4b656369f125b">car</run></td>
</tr>
</table>
</li>
<li>
Durch Anwendung des λ-Ausdrucks <math>M</math> auf den λ-Ausdruck <math>N</math> entsteht wieder ein λ-Ausdruck, geschrieben als <math>(M N)</math>.<br />
'''Beispiele:'''
<table>
<tr>
<td><run id="4b656369f21f9">(sqrt pi)</run></td>
<td><run id="4b656369f31a0">(car '(a b))</run></td>
</tr>
</table>
</li>
<li>
λ-Ausdrücke können durch funktionale Abstraktion erzeugt werden:
Mit der Variablen <math>x</math> und dem λ-Ausdruck <math>M</math> ist auch
<math>(\lambda (x) M)</math> ein λ-Ausdruck.<br />
'''Beispiele:'''
<table>
<tr>
<td><run id="4b656369f4137">(lambda (x) (* 2 x))</run></td>
<td><run id="4b65649f54168">(lambda (ls) (car ls))</run></td>
</tr>
<tr>
<td><run id="4b65636a01e46">((lambda (x) (* 2 x)) 12)</run></td>
<td><run id="4b65649f550f3">((lambda (ls) (car ls)) '(a b))</run></td>
</tr>
</table>
</li>
</ol>
== Rechenregeln ==
<code id="4b70ad855c47f" hidden>
(define super-element?
(lambda (el ls)
(cond
[(not (list? ls)) (if (eqv? el ls) #t #f)]
[(null? ls) #f]
[(list? (car ls)) (or (super-element? el (car ls)) (super-element? el (cdr ls)))]
[(eqv? el (car ls)) #t]
[else
(super-element? el (cdr ls))])))
(define super-streichen
(lambda (el ls)
(cond
[(not (list? ls)) ls]
[(null? ls) ls]
[(list? (car ls)) (cons (super-streichen el (car ls)) (super-streichen el (cdr ls)))]
[(eqv? el (car ls)) (super-streichen el (cdr ls))]
[else
(cons (car ls) (super-streichen el (cdr ls)))])))
(define super-ersetzen
(lambda (el1 ls el2)
(cond
[(not (list? ls)) ls]
[(null? ls) ls]
[(list? (car ls)) (cons (super-ersetzen el1 (car ls) el2) (super-ersetzen el1 (cdr ls) el2))]
[(eqv? el1 (car ls)) (cons el2 (super-ersetzen el1 (cdr ls) el2))]
[else
(cons (car ls) (super-ersetzen el1 (cdr ls) el2))])))
(define operator-liste?
(lambda (ausdr)
(and (list? ausdr) (not (null? ausdr)) (or (symbol? (car ausdr)) (procedure? (eval (car ausdr)))))))
(define lambda-term?
(lambda (ausdr)
(and (list? ausdr) (= (length ausdr) 3)
(eqv? (car ausdr) 'lambda)
(list? (cadr ausdr)) (= (length (cadr ausdr)) 1) (symbol? (caadr ausdr))
(or (symbol? (caddr ausdr)) (number? (caddr ausdr)) (operator-liste? (caddr ausdr))))))
(define lambda-anwendung?
(lambda (ausdr)
(and (list? ausdr) (= (length ausdr) 2)
(lambda-term? (car ausdr)))))
(define lambda-ausdruck?
(lambda (ausdr)
(or (symbol? ausdr)
(operator-liste? ausdr)
(lambda-term? ausdr))))
(define beta-left
(lambda (term)
(let ([var (caadar term)] [ausdr1 (caddar term)] [ausdr2 (cadr term)])
(super-ersetzen var ausdr1 ausdr2))))
;Beta-Reduktion
(define beta
(lambda (term)
(cond
[(not (list? term)) term]
[(null? term) term]
[(lambda-anwendung? term) (beta (beta-left term))]
[(list? (car term))
(cons (beta (car term)) (beta (cdr term)))]
[else
(cons (car term) (beta (cdr term)))])))
(define alphabet '(a b c d e f g h i j k l m n o p q r s t u v w x y z))
(define suche-var
(lambda (var term ausdr alphabet)
(cond
[(and (not (super-element? var term)) (not (super-element? var ausdr))) var]
[(null? alphabet) var]
[else
(suche-var (car alphabet) term ausdr (cdr alphabet))])))
(define alpha-test
(lambda (term ausdr)
(cond
[(not (list? term)) term]
[(null? term) term]
[(lambda-term? term)
(let ([var (caadr term)] [neuterm (caddr term)])
(cond
[(super-element? var ausdr)
(let ([neuvar (suche-var var term ausdr alphabet)])
(super-ersetzen var term neuvar))]
[(lambda-term? neuterm) (list 'lambda (list var) (alpha-test neuterm ausdr))]
[else
term]))]
[(list? (car term))
(cons (alpha-test (car term) ausdr) (alpha-test (cdr term) ausdr))]
[else
(cons (car term) (alpha-test (cdr term) ausdr))])))
(define alpha-ersetzen
(lambda (term alphabet)
(cond
[(not (lambda-term? term)) term]
[(null? alphabet) term]
[(eqv? (car alphabet) (caadr term)) (alpha-ersetzen term (cdr alphabet))]
[(super-element? (car alphabet) (cddr term)) (alpha-ersetzen term (cdr alphabet))]
[else
(super-ersetzen (caadr term) term (car alphabet))])))
(define lambda-ersetzen
(lambda (term var)
(if (lambda-term? term)
(let ([geb-var (caadr term)] [op-term (caddr term)])
(cond
[(super-element? var op-term) term]
[(lambda-term? op-term) (list 'lambda (list geb-var) (lambda-ersetzen op-term var))]
[else
(super-ersetzen geb-var term var)]))
term)))
;Alpha-Konvention
(define alpha
(lambda (term)
(cond
[(not (list? term)) term]
[(null? term) term]
[(lambda-anwendung? term)
(let ([var (caadar term)] [lambda-term (car term)] [ausdr (cadr term)])
(list (alpha-test lambda-term ausdr) ausdr))]
[(list? (car term))
(cons (alpha (car term)) (alpha (cdr term)))]
[else
(cons (car term) (alpha (cdr term)))])))
(define eta-einf
(lambda (term)
(if (lambda-term? term)
(let ([var (caadr term)] [ausdr (eta-einf (caddr term))])
(cond
[(lambda-term? ausdr)
(eta-einf (list 'lambda (list var) ausdr))]
[(lambda-anwendung? ausdr)
(list (eta-einf (car ausdr)) (eta-einf (cadr ausdr)))]
[(and (list? ausdr) (= (length ausdr) 2))
(let ([ausdr-li (eta-einf (car ausdr))] [ausdr-re (eta-einf (cadr ausdr))])
(if (eqv? var ausdr-re)
ausdr-li
(list ausdr-li ausdr-re)))]
[else
term]))
term)))
(define eta-mehrf
(lambda (term)
(cond
[(not (list? term)) term]
[(null? term) term]
[(lambda-term? term) (eta-einf term)]
[(lambda-anwendung? term) (list (eta-mehrf (car term)) (eta-mehrf (cadr term)))]
[(list? (car term))
(cons (eta-mehrf (car term)) (eta-mehrf (cdr term)))]
[else
(cons (car term) (eta-mehrf (cdr term)))])))
;Eta-Umwandlung
(define eta
(lambda (term)
(let ([term-neu (eta-mehrf term)])
(if (equal? term term-neu)
term
(eta term-neu)))))
</code>
=== β-Reduktion ===
Sind <math>E</math> und <math>M</math> λ-Ausdrücke, wobei <math>M</math> in <math>E</math> nicht gebunden vorkommen darf, und <math>x</math> eine Variable, so beschreibt
<blockquote><math>((\lambda (x) E) M)\stackrel{\beta}{\longrightarrow}E[M/x]</math></blockquote>
<blockquote>(sprich: "<math>E</math> mit <math>M</math> für <math>x</math>")</blockquote>
das Ergebnis der Anwendung von <math>(\lambda (x) E)</math> auf <math>M</math>.<br />
Dabei werden im Ausdruck <math>E</math> alle Variablen <math>x</math> durch den Ausdruck <math>M</math> ersetzt.<br />
'''Beispiele:'''
<run id="4b65fa256c8fe">
(beta '((lambda (x) (* 2 x)) 12))
</run>
<run id="4b65fa256d89a">
(beta '((lambda (x) (+ (* x x) 2)) ((lambda (y) (- y 5)) 3)))
</run>
'''Beachte:'''<br />
Der λ-Kalkül fordert eine sogenannte ''leftmost reduction'' bzw. ''normal order reduction'', d.h. der jeweils links stehende Ausdruck <math>(\lambda (x) E)</math> wird zuerst reduziert.
=== α-Konvention ===
Wenn <math>x'</math> in <math>E</math> nicht frei vorkommt, gilt:
<blockquote>
<math>(\lambda (x) E)\stackrel{\alpha}{\longleftrightarrow}(\lambda (x') E[x'/x])</math>
</blockquote>
<blockquote>
(sprich: "Ersetze alle <math>x</math> in <math>(\lambda (x) E)</math> durch <math>x'</math>")</blockquote>
'''Beispiele:'''
<run id="4b65e583945d7">
(alpha-ersetzen '(lambda (x) (* y x)) '(x y z))
</run>
<run id="4b845c1b206d8">
(alpha '(((lambda (op) (lambda (y) (op y))) y) w))
</run>
=== η-Umwandlung ===
Vereinfachungsregel, mit:
<blockquote><math>(\lambda (x) (E x))\stackrel{\eta}{\longrightarrow}E</math></blockquote>
'''Beispiele:'''
<run id="4b65eafff086f">
(eta '(lambda (x) (sqrt x)))
</run>
<run id="4b65eafff17f8">
(eta '((lambda (x) (sqrt x)) ((lambda (y) (sin y)) 1)))
</run>
== Aufgaben ==
<code id="4b69ebeb48146" hidden>
(define listequal?
(lambda (ls1 ls2)
(cond
[(and (null? ls1) (null? ls2)) #t]
[(or (null? ls1) (null? ls2)) #f]
[(and (list? (car ls1)) (list? (car ls1)))
(and (listequal? (car ls1) (car ls2)) (listequal? (cdr ls1) (cdr ls2)))]
[else
(and (equal? (car ls1) (car ls2)) (listequal? (cdr ls1) (cdr ls2)))])))
</code>
<ol>
<li>
Implementieren sie die Prozedur <tt>super-ersetzen</tt>, die in einer verschachtelten Liste das Element 1 durch das Element 2 ersetzt.
<code id="4b69ebeb490d7">
(define super-ersetzen
(lambda (el1 ls el2)
...))
</code>
<check>
(and (listequal? (super-ersetzen 'a '(a b a c a d) 'x) '(x b x c x d))
(listequal? (super-ersetzen 'x '(lambda (x) (* 2 x)) 'z) '(lambda (z) (* 2 z))))
</check>
<run id="4b69f75ea6ea2">
(super-ersetzen 'x '(lambda (x) (+ (* 2 x) y)) 'z)
</run>
</li>
<li>
Schreiben Sie das Prädikat <tt>lambda-term?</tt>, das prüft, ob eine vorgegebene Liste ein λ- Ausdruck der Form <math>(\lambda (x) E)</math> ist.
<code id="4b69f75ea5f07">
(define lambda-term?
(lambda (ausdr)
...))
</code>
<check>
(and (lambda-term? '(lambda (x) (* 2 x)))
(lambda-term? '(lambda (z) (* (sin z) y)))
(not (lambda-term? '(- x y z))))
</check>
<run id="4b69f75ea7e43">
(lambda-term? '(lambda (x) (+ (* 2 x) y)))
</run>
</li>
Implementieren Sie die nächste Aufgabe unter ''DrScheme''.
Denken Sie an die Definition geeigneter Hilfsprozeduren.
<li>
Schreiben Sie die Prozedur <tt>(lambda-ersetzen <term> <var>)</tt>, die in einem λ-Term (vgl. Aufgabe 2) alle gebundenen Variablen durch die Variable <tt>var</tt> ersetzt. Natürlich darf <tt>var</tt> in diesem λ-Term nicht frei vorkommen.<br />
Beachten Sie dabei, dass λ-Terme selbst beliebige λ-Terme enthalten können
(Repräsentation als verschachtelte Listen).
</li>
</ol>
== Zum Weiterarbeiten ==
=== Currying ===
Der λ-Kalkül gestattet nur die Definition einstelliger Funktionen. In Scheme haben wir darüber hinaus nullstellige, mehrstellige und variabelstellige Prozeduren kennen gelernt.<br />
Um mehrstellige Prozeduren auf einstellige zurückzuführen, greifen wir die Idee der Prozeduren höherer Ordnung auf:
<code id="4b8ab8b74a977">
(define summe-curry
(lambda (x)
(lambda (y)
(if (<= y 0)
x
((summe-curry (+ x 1)) (- y 1))))))
</code>
<run id="4b8ab8b74b901">
((summe-curry 3) 5)
</run>
Diese Technik lässt sich auf beliebige n-stellige Prozeduren erweitern.
Sie bezeichnet man als ''Currying'' oder ''Curryfication'', benannt nach dem US-amerikanischen Logiker und Mathematiker '''Haskell Brooks Curry''' (* 12. September 1900; † 1. September 1982).
=== Y-combinator ===
Die Sonderform <tt>define</tt> ist im λ-Kalkül ebenfalls nicht vorgesehen.
Zunächst scheint das kein Problem zu sein, da in Scheme unbenannte Prozeduren zur Anwendung kommen können. Doch wie lassen sich dann ''rekursive'' Prozeduren definieren?<br />
Wir benötigen dazu eine spezielle Funktion <math>Y</math>, die als "Rekursivmacher" fungiert.<br />
Von einer derartigen Funktion muss die sogenannte Fixpunkt-Eigenschaft erfüllt werden:
<blockquote><math>(Y f) = (f (Y f))</math></blockquote>
Ohne an dieser Stelle den Nachweis zu führen, erfüllt die Definition des ''Y-combinators'' diese Forderung:
<code id="4b8ad0a8e7a6f">
(define Y
(lambda (f)
((lambda (x) (f (lambda (y) ((x x) y))))
(lambda (x) (f (lambda (y) ((x x) y)))))))
</code>
Wir nutzen zunächst die Kurzform <tt>Y</tt> zur rekursiven Definition der Fakultätsfunktion:
<run id="4b8bf8fcdcf23">
(Y
(lambda (fak)
(lambda (n)
(if (= n 0)
1
(* n (fak (- n 1)))))))
</run>
Natürlich lässt sich diese Fakultätsfunktion auch anwenden. Wir berechnen z.B. <math>5!=120</math>:
<run id="4b8ad0a8e89f8">
((Y
(lambda (fak)
(lambda (n)
(if (= n 0)
1
(* n (fak (- n 1))))))) 5)
</run>
Abschließend ersetzen wir <tt>Y</tt> durch den entsprechenden λ-Ausdruck.
Nun benötigen wir für rekursive Definitionen tatsächlich kein <tt>define</tt>!
<run id="4b8ad0a8e9a16">
(((lambda (f)
((lambda (x) (f (lambda (y) ((x x) y))))
(lambda (x) (f (lambda (y) ((x x) y))))))
(lambda (fak)
(lambda (n)
(if (= n 0)
1
(* n (fak (- n 1))))))) 5)
</run>