Skip to content

seppeljordan/learnscheme

Repository files navigation

LISP-Kurs

License

Copyright (C) 2015 Sebastian Jordan. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled “GNU Free Documentation License”.

Anleitung fuer dieses Dokument

Dieses Dokument enthaelt sehr viel Schemecode. Du kannst den Code extrahieren, sofern du GNU Make und Emacs auf deinem System installiert hast. Fuehre einfach den Befehl

make all

im Verzeichnis aus, in dem die Datei README.org liegt. Eine pdf-Version dieses Dokuments wird ebenfalls erzeugt, sofern latex auf deinem System installiert ist.

Einfuehrung

Dieser Text soll dabei Helfen, einen konzeptuellen Zugang zum Schreiben von Computerprogrammen aus einer funktionalen Perspektive zu geben.

Themen

  • Rekursion
  • Datenstrukturen
  • Streams & Lazy evaluation
  • Concurrency

Material

Emacs
Programmiereditor mit gutem LISP/Scheme-Support
Vim
Standard Linuxtexteditor der auf fast allen Systemen installiert ist (Vi)
Structure and Interpretation of Computer Programs
Ein wirklich sehr gutes Lehrbuch zum Thema, das den Author stark beeinflusst hat.
Guile
Wir benutzen Guile als Schemeinterpreter
org-mode
Dieses Dokument ist mit Hilfe von org-mode verfasst.

Ein Beispiel: Wurzel nach Newtonverfahren

(define (sqrt-newton x)
  ;; iteration step
  (define (sqrt-iter guess)
    (if (good-enough? guess)
        guess
        (sqrt-iter (improve guess))))

  ;; test if we are already good enough with our guess
  (define (good-enough? guess)
    (<= (abs (- (square guess) x)) 0.0001))

  ;; square a number
  (define (square n) (* n n))

  ;; improve a guess
  (define (improve guess)
    (avarage guess (/ x guess)))

  (define (avarage a b)
    (/ (+ a b) 2.0))

  ;; start the iteration with 1 as initial guess
  (sqrt-iter 1))

;; test the function
(define test (sqrt-newton 4))
(display "Die Wurzel von 4 ist ")
(display test)
test

Diese Beispiel findest du unter dem Namen examples/sqrt-newton.scm, wenn du die .scm-Dateien gemaess der obigen Anleitung erzeugt hast.

Wiederholung: Funktionen & Blockstruktur

Aufgabe 1.3

Hier ist eine Beispielloesung fuer die Aufgabe 1.3 aus dem Buch:

(define (add-and-mult a b c)
  (define (second-greatest m n o)
    (if (and (<= m n) (<= m o))
        (min n o)
        (if (and (<= n m) (<= n o))
            (min m o)
            (min m n))))
  (define greatest max)
  (define (square x) (* x x))
  (+ (square (second-greatest a b c))
     (square (greatest a b c))))

;; test the function
(add-and-mult 4 2 3)

Das Ergebnis des Tests:

Aufgabe 1.8

Zum Loesen der Aufgabe 1.8 verwenden wir die selbe Strategie wie fuer das Finden der Quadratwurzel. Wir veraendern allerdings die improve-Funktion.

(define (cuberoot-newton x)
  (define (cubert-iter guess)
    (if (good-enough? guess)
        guess
        (cubert-iter (improve guess))))
  (define (good-enough? guess)
    (>= 0.001
        (abs (- (cube guess) x))))

  ;; new improve function
  (define (improve guess)
    (/ (+ (* 2. guess) (/ x (square guess)))
       3.))

  (define (cube n) (* n (square n)))
  (define (square n) (* n n))
  (cubert-iter 1))

;; test the function
(cuberoot-newton 125.0)

Rekursion

Wir wollen die Fakultaet einer Zahl berechnen. Dazu uebertragen wir die Definition der Fakultaet in Scheme.

\begin{align} !x &= x ⋅ !(x - 1)
!0 &= 1 \end{align}

Eine intuitivie Definition der Fakultaet koennte folgende sein:

(define (factorial x)
  (if (<= x 1)
      1                           ;; base case
      (* x (factorial (- x 1))))) ;; recursive step

;; test the function
(factorial 6)
720

Dies ist eine vereinfachte Darstellung der Auswertung der obigen Funktion. Wie wir sehen koennen, benoetigt die Funktion “linear viel” Speicher.

(factorial 5)
(* 5 (factorial 4))
(* 5 (* 4 (factorial 3)))
(* 5 (* 4 (* 3 (factorial 2))))
(* 5 (* 4 (* 3 (* 2 (factorial 1)))))
(* 5 (* 4 (* 3 (* 2 1))))
(* 5 (* 4 (* 3 2)))
(* 5 (* 4 6))
(* 5 24)
120

Unser Ziel ist es, nur konstant viel Speicher – also unabhaengig von der eingegebnene Zahl – zu verbrauche. Dazu wandeln wir die rekursive Definition der Fakultaet in einen iterativen Prozess um.

(define (factorial x)
  (define (iter accu count)
    (if (= count x)
        (* count accu)
        (iter (* accu count) (+ count 1))))
  (iter 1 1))

(factorial 5)
120

Fuer eine Iteration benoetigen wir (genauso wie in Python und Co) nur konstant viel Speicher. Hier ist dargestellt wie der Interpreter den Funktionskoerper der obigen Funktion auswerten koennte.

(define x 3)
(iter 1 1)
(iter 1 2)
(iter 2 3)
6

Fibonacci

Die ersten 10 Elemente der Fibonaccireihe.

fib(n)0112358132134
n12345678910

Eine rekursive Definition der Reihe: \begin{equation} fib(n) = fib(n -1) + fib(n - 2) \end{equation}

Hier ist eine Pseudoimplementation der Fibnoaccireihe im imperativen Stil:

int a <- 0
int b <- 1
for i in (3..n)
  int c <- a + b
  a <- b
  b <- c
return b

Als Uebungsvorschlag: Du koenntest versuchen, die Fibonaccireihe als Rekursion & Iteration zu implementieren (Siehe Fakultaet).

Eine rekursive Beispielimplementation fuer die Fibonaccireihe:

(define (fib-rec n)
  (cond ((< n 1) (error "FIB-REC: index to small"))
        ((= n 1) 0)
        ((= n 2) 1)
        ((> n 2) (+ (fib-rec (- n 1)) (fib-rec (- n 2))))))
(fib-rec 10)
34

Hier ist eine iterative Beispielimplementation der Fibonaccizahlen.

(define (fib-iter n)
  (define (iter counter n-1 n-2)
    (if (= counter n)
        n-1
        (iter (1+ counter) (+ n-1 n-2) n-1)))

  (cond ((= n 1) 0)
        ((= n 2) 1)
        (else (iter 2 1 0))))
(fib-iter 10)
34

Listen

Listen bestehen aus Paaren.

Paare

Paare sind zusammengesetzte Datenstrukturen, das heisst, dass sich Paare in kleinere Bestandteile zerlegen lassen und, vor allem, sich aus kleineren Bestandteilen zusammen bauen lassen.

Paare sind “Behaelter”, die genau 2 Werte speichern koennen. Es gibt einen “ersten” Wert und einen “zweiten” Wert, die eindeutig Adressierbar sein muessen.

Es folgt eine Beispielinterface fuer das Programmieren mit Paaren:

(define (pair a b)
  (error "PAIR: undefined"))
(define (1st p)
  (error "1ST: undefined"))
(define (2nd p)
  (error "2ND: undefined"))

;; What would you get?
(1st (pair 1 2)) ;; 1
(2nd (pair 1 2)) ;; 2

(1nd (2nd (2nd (pair 1
                     (pair 2
                           (pair 3
                                 4))))))
;; 3

Und in Scheme?

In Scheme sind die pair-, 1st- und 2nd-Funktion schon definiert.

  • pair == cons
  • 1st == car
  • 2nd == cdr

Beispiel fuer car:

(define new-pair (cons 1 2))
(car new-pair)
1

Beispiel fuer cdr:

(define new-pair (cons 1 2))
(cdr new-pair)

Andere nuetzliche Funktionen im Zusammenhang mit Paaren:

(pair? (cons 1 2)) ;; #t
(pair? 1) ;; #f

#nil ;; #nil ist der sogenannte Nullzeiger und signalisiert KEINEN
     ;; Wert.
(null? #nil) ;; #t
(null? 1) ;; #f

Definition der Primitiven

Hier ist eine Definition von “Paaren” (ohne pair?).

(define (my-cons a b)
  (define (dispatch mode)
    (cond ((= mode 1) a)
          ((= mode 2) b)
          (else (error "COND: Argument not [1..3] -- " mode))))
  dispatch)

(define (my-car list)
  (list 1))

(define (my-cdr list)
  (list 2))

Auswertung der Implementation per Befehlssubstitution:

;; testevaluation (KOMMENTIEREN)
(my-car (my-cons 5 8))
;; zuerst werten wir den Rueckgabewert von my-cons aus.  my-cons gibt
;; uns eine Funktion zurueck (dispatch), die hier durch das lambda
;; dargestellt wird.
(my-car (lambda (mode) (cond ((= mode 1) 5)
                             ((= mode 2) 8))))

;; Jetzt wird my-car ausgewertet.  my-car "bewirkt" dass das Argument
;; (also in diesem Fall die "lambda"-Funktion ein Argument bekommt und
;; dann ausgewertet wird.
((lambda (mode)
   (cond ((= mode 1) 5)
         ((= mode 2) 8)))
 1)

;; Nun wird der Aufruf der "lambda"-Funktion durch den Koerper der
;; Funktion ersetzt.
((define mode 1)
 (cond ((= mode 1) 5)
       ((= mode 2) 8)))
5

Jetzt wirklich Listen

Listen sind in Scheme einfach nur “geschachtelte” Paare.

;; definitions-lists
;;;;;;;;;;;;;;;;;;;;

(define empty-list #nil)

(define (list-empty? list) (null? list))

;; put an element in front of the list
(define (prepend elem list)
  (cons elem list))

;; put an element in the end of a list
(define (append list elem)
  (if (null? list)
      (cons elem #nil)
      (cons (car list) (append (cdr list) elem))))

;; get the first element of a list
(define (head list)
  (cond ((pair? list) (car list))
        ((null? list) (error "HEAD: list is empty"))
        (else (error "HEAD: object is not a list"))))

;; get all but the first element of a list
(define (tail list)
  (cond ((pair? list) (cdr list))
        ((null? list) (error "TAIL: list is empty"))
        (else (error "TAIL: object is not a list"))))

;; get all but the last element of a list
(define (init list)
  (cond ((null? list) (error "INIT: empty list given"))
        ((null? (cdr list)) #nil)
        (else (cons (car list)
                    (init (cdr list))))))

;; get the last element of a list
(define (last list)
  (cond ((null? list) (error "LAST: empty list given"))
        ((null? (cdr list)) (car list))
        (else (last (cdr list)))))

;; get the n-th element of a list (starting with 0)
(define (index list n)
  (if (= n 0)
      (car list)
      (index (cdr list) (1- n))))

Generalisierung

Wenn wir mit Listen zu tun haben, dann kommen bestimmte “Probleme” oft vor. Betrachten wir zum Beispiel das folgende Stueckchen Code:

<<definitions-lists>>

;; This procedure adds 1 to every element
(define (add-one list)
  (if (list-empty? list)
      empty-list
      (prepend (1+ (head list))
               (add-one (tail list)))))

(define numbers '(1 2 3 4))
(display "The original list is ")
(write numbers)
(newline)
(display "add-one applied to the list results in ")
(write (add-one numbers))
(newline)

;; This procedure multiplies every element by 2
(define (mult-two list)
  (if (list-empty? list)
      empty-list
      (prepend (* 2 (head list))
               (mult-two (tail list)))))

(define numbers '(1 2 3 4))
(display "The original list is ")
(write numbers)
(newline)
(display "mult-two applied to the list results in ")
(write (mult-two numbers))
(newline)

Beide Funktionen machen etwas sehr Aehnliches. Es wird ueber eine Liste iteriert. Dabei wird auf jedes Element eine Operation angewendet und so eine neue Liste erzeugt.

(define (<function> list)
  (if (list-empty? list)
      empty-list
      (prepend (<operation> (head list))
               (<function> (tail list)))))

Die Generalisierung dieser beiden Funktionen wird map genannt. Wir koennen diese Idee allgemein in Scheme formulieren:

(define (map operation list)
  (if (list-empty? list)
      empty-list
      (prepend (operation (head list))
               (map operation (tail list)))))

Im folgenden Stueckchen Code benutzen wir map um weitere Funktionen zu definieren.

<<definitions-lists>>
<<definition-map>>

(define (add-one list)
  (map 1+ list))

(define (mult-two list)
  (map (lambda (x)
         (* 2 x))
       list))

(write (mult-two '(1 2 3 4 5)))
(newline)
(write (add-one '(1 2 3 4 5)))
(newline)

Hausaufgabe

Wir stellen uns einmal vor, dass wir eine Liste von Zahlen gegeben haben und wollen alle Zahlen aufsummieren. Der Code dafuer wurde wohl in etwa folgendermasse aussehen:

;; We have to include the definitions for our list primitives
<<definitions-lists>>

(define (sum-list list)
  (define (iter accu current)
    (if (list-empty? current)
        accu
        (iter (+ accu (head current)) (tail current))))
  (iter 0 list))

(define numbers '(1 2 3 4 5 6))
(display "The sum of ")
(write numbers)
(display " is ")
(write (sum-list numbers))
(newline)

Wie koennen wir diese Funktion generalisieren? Wenn du im Internet recherchieren willst, dann suche nach den Stichworten fold, left fold, foldl, wie zum Beispiel hier (der Link funktioniert aus irgendeineem Grund nicht auf der github-Seite) geschehen.

Loesung

Die sum-list Funktion macht prinzipiell 2 Dinge:

  1. Die Funktion iteriert ueber die List (so wie in map).
  2. Die Funktion akkumuliert Werte, die in der Liste gespeichert sind mittels einer Kombinationsfunktion.
(define (foldl accu-fun start list)
  (cond ((list-empty? list) start)
        (else (foldl accu-fun
                     (accu-fun start (head list))
                     (tail list)))))

Wir koennen nun die foldl-Funktion fuer verschiedene Dinge nutzen:

;; We have to include list primitives
<<definitions-lists>>
;; ... and foldl
<<definition-foldl>>
<<definition-map>>

;; The sum function
(define (sum list) (foldl + 0 list))

;; The length function
(define (length list)
  (foldl (lambda (accu e)
           (1+ accu))
         0
         list))

;; We can even define a filter function
(define (filter predicate list)
  (foldl (lambda (accu-list current)
           (if (predicate current)
               (append accu-list current)
               accu-list))
         empty-list
         list))
(define (filter pred list)
  (foldr (lambda (x accu)
           (if (pred x)
               (prepend x accu)
               accu))
         empty-list
         list))

foldr

Manchmal wollen wir aber auch ueber eine Liste von “hinten” aus iterieren. Aehnlich wie foldl wollen wir eine Accumulationsfunktion und einen Startwert angeben koennen. Die Funktion soll dabei jedes Element nur einmal ansehen.

(define (foldr f start list)
  (cond ((null? list) start)
        (else (f (car list)
                 (foldr f start (cdr list))))))

Hausaufgaben

mkList
Hat 1 Argument, n. Soll Liste der Laenge n erzeugen, mit nur 1en drin.
(define (mkList n)
  (cond ((= n 0) #nil)
        (else (cons 1 (mkList (- n 1))))))

    
mkNumbers
Hat 1 Argument, n. Soll Liste erzeugen, mit den Zahlen 1 bis n.
(define (mkNumbers n)
  (define (iter current)
    (cond ((= current n) #nill)
          ((< current n)
           (cons (+ 1 current) (iter (+ 1 current))))
          (else (error "Internal error"))))
  (iter 0))

(define (mkNumbers2 n)
  (define (iter current acc)
    (cond ((= current n) acc)
          ((< current n) (iter (+ 1 current) (append acc (+ 1 current))))))
  (iter 0 #nil))

(define (mkNumber3 n)
  (if (= n 0)
      #nil
      (append (mkNumber3 (- n 1)) n)))


    
iter-list
Hat 3 Argumente
iter-fun
Ist eine Funktion, die ein Argument hat
start-val
Hat den passenden Typen zu iter-fun
n
Integer, so viele Elemente soll die Liste am Ende haben
(define (iter-list fun start len)
  (define (iter current current-elem)
    (cond ((= current len) #nil)
          ((< current len)
           (cons current-elem (iter (+ 1 current) (fun current-elem))))
          (else (error "Internal error"))))
  (iter 0 start))
    
(iter-list 1+ 0 10)
;; '(0 1 2 3 4 5 6 7 8 9)

(iter-list (lambda (x) (cons 1 x)) empty-list 3)
;; '(#nil '(1) '(1 1))

The List dropth, the List taketh

Manchmal sind wir an den ersten n Elementen einer Liste interessiert. Wir koennen dann entsprechend oft head und tail anwenden.

(define (first-3-elems xs)
  (list (head xs) (head (tail xs)) (head (tail (tail xs)))))

Die Funktion first-3-elems nimmt eine Liste entgegen und gibt eine neue Liste zurueck, die die ersten 3 Elemente enthaelt. Wir koennen diese Funktion zu einer allgemeineren Funktion abstrahieren, die eine Liste und eine Zahl n entgegen nimmt und die ersten n Elemente der Liste zurueck gibt.

(define (take n xs)
  (cond ((= n 0) empty-list)
        ((list-empty? xs)
         (error "-- TAKE: tried to get an element from the empty list"))
        (else (cons (head xs) (take (1- n) (tail xs))))))

Analog dazu koennen wir auch eine Funktion definieren, die die ersten n Elemente einer Liste verwirft und den “Rest” zurueck gibt.

(define (drop n xs)
  (cond ((= 0 n) xs)
        ((list-empty? xs)
         (error "-- DROP: cannot drop another element from the empty list"))
        (else (drop (1- n) (tail xs)))))

Was noch fehlt… Sortieren!

Wir haben gelernt, wie wir

  • Listen (mit Hilfe von “higher order functions” erzeugen koenne
  • primitive Operationen auf Listen durchfuehren koennen, die einzelne Elemente der Liste manipulieren
  • wiederkehrende Operationen abstrahieren koennen und “higher order functions” nutzen koennen um weniger ( = besseren) Code zu schreiben.

Wir haben noch nicht gelernt, wie wir Listen sortieren. Hier ist eine Beispielimplementation von Quicksort. Sie sortiert eine Liste von Zahlen aufsteigend der Groesse nach.

<<definitions-lists>>
<<definition-map>>
<<definition-foldl>>
<<definition-foldr>>
<<definition-filter>>

(define (concat l1 l2)
  (foldr (lambda (x accu)
           (prepend x accu))
         l2
         l1))

(define (concat3 l1 l2 l3)
  (concat l1
          (concat l2 l3)))

(define (quicksort numbers)
  (define (qs)
    (let* ((pivot (head numbers))
           (lower (filter (lambda (x) (< x pivot))
                          (tail numbers)))
           (bigger (filter (lambda (x) (>= x pivot))
                           (tail numbers))))
      (begin
        (write lower)
        (display " ")
        (write pivot)
        (display " ")
        (write bigger)
        (newline)
        (concat3 (quicksort lower)
                 (list pivot)
                 (quicksort bigger)))))

  (cond ((list-empty? numbers) empty-list)
        (else (qs))))

Leider ist die Verwendung dieser Funktion darauf beschraenkt, Zahlen der Groesse nach zu sortieren. Wir koennen diese Beispielimplementation abstrahieren, indem wir “offen” lassen, welche Vergleichsoperation beim Vergleich verwendet werden soll. Auf diese Art koennen wir alle Listen nach beliebigen Kriterien sortieren.

(define (quicksort smaller-than xs)
  (if (list-empty? xs)
      empty-list
      (let*
          ((pivot (head xs))
           (non-pivot (tail xs))
           (< (lambda (x) (smaller-than x pivot)))
           (>= (lambda (x) (not (smaller-than x pivot))))
           (smaller (filter < non-pivot))
           (bigger (filter >= non-pivot)))
        (concat3 (quicksort smaller-than smaller)
                 (list pivot)
                 (quicksort smaller-than bigger)))))

;; Hier ist noch eine Implementation von mergesort
(define (mergesort smaller-or-equal-than xs)
  (define (merge as bs)
    (cond ((list-empty? as) bs)
          ((list-empty? bs) as)
          (else (let
                    ((a (head as))
                     (b (head bs)))
                  (if (smaller-or-equal-than a b)
                      (prepend a
                               (merge (tail as) bs))
                      (prepend b
                               (merge as (tail bs))))))))
  (let*
      ((len (length xs))
       (first-half (take (quotient len 2) xs))
       (second-half (drop (quotient len 2) xs)))
    (if (<= (length xs) 1)
        xs
        (merge (mergesort smaller-or-equal-than
                          first-half)
               (mergesort smaller-or-equal-than
                          second-half)))))

Der Vollstaendigkeit halber, hier noch einmal die Definition von concat3 sauber notiert.

(define (concat l1 l2)
  "Concatenate l1 with l2"
  ;; We choose foldr to prepend all the elements of l1 to l2.  If we
  ;; chose to fold from the left and append every element of l2 to l1,
  ;; we would had a runtime behavior of O(n*m + n^2/2) where
  ;;
  ;; * n = length of l1
  ;; * m = length of l2
  ;;
  ;; This way we have O(n) as runtime behavior. (Why?)
  (foldr (lambda (current accu)
           (prepend current accu))
         l2
         l1))

(define (concat3 l1 l2 l3)
  "Concatenate 3 lists l1 l2 l3"
  ;; First we concatenate l3 and l2, which in turn gets concatenated
  ;; with l1, which gives us a runtime behavior of $ O(n + m) $ where
  ;;
  ;; * n = length of l1
  ;; * m = length of l2
  ;; ( What would be the runtime behavior of
  ;;   (concat (concat l1 l2) l3)
  ;;   ?)
  (concat l1
          (concat l2
                  l3)))

Zusammenfassung, eine kleine Library

Bis hier her haben wir uns angesehen, was wir alles mit Listen anstellen koennen. Wir haben gelernt wie wir Listen als Paare darstellen koennen und haben sogar Paare als Funktionen dargestellt. Wir haben mathematische Probleme effizient geloest (Fibonacci, Fakultaet) und daraus wiederkehrende Prozesse zu Funktionen abstrahiert. Die Funktionen, die wir dabei definiert haben, koennen wir zu einer Library zusammenfassen. Wir nennen sie lists.scm.

<<definitions-lists>>
<<definition-map>>
<<definition-foldl>>
<<definition-foldr>>
<<definition-filter>>
<<definition-iter-list>>
<<definition-take>>
<<definition-drop>>
;; We have to define concat3 before the sorting algorithms because we
;; use these in their definition.
<<definition-concat>>
<<definition-sort>>

Binaere Suchbaeume

Baeume sind genauso wie Listen in erster Linie eine Abstraktion ueber Daten. Listen abstrahieren Daten als eine Sequence die von vorne nach hinten durchgeblaettert werden kann. Das soll uns ermoeglichen, ueber Daten als eine Einheit nachdenken zu koennen.

Listen sind fuer viele Dinge gut, vor allem wenn es um iterative Prozesse geht. Fuer manche Dinge eignen sich Listen allerdings nicht so gut, wie zum Beispiel das Finden von Daten, welches nur mit einer Zeitkomplexitaet von $O(n)$ realisiert werden kann, selbst wenn die Liste bereits sortiert ist. Listen sind auch nicht so toll, wenn es um das hinzufuegen neuer Daten geht. Das hinzufuegen eines Elements zum Beginn einer Liste geht schnell, aber alles andere dauert viel laenger. Zur Erinnerung: Die append-Funktion muss bis ans Ende der Liste iterieren, wenn es Element angehaengt werden soll.

Struktur

Baeume sind, so wie Listen, rekursive Datenstrukturen. Fuer Listen beduetet dass, dass jede Liste entweder leer ist, oder die Verkettung eines Elements mit einer List. Baeume sind sehr aehnlich definiert: Jeder Baum ist entweder leer oder ein Element verkettet mit zwei Unterbaeumen. Die Unterbaeume heissen “linker Unterbaum” und “rechter Unterbaum”.

;; Definitions of tree constructors
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; a leaf is just an empty list
(define leaf empty-list)
(define leaf? null?)

;; a branch is a value and two subtrees
(define (branch val left right)
  (list val left right))
(define branch-value car)
(define branch-left cadr)
(define branch-right caddr)

Die Baeume, die wir hier betrachten wollen heissen “binaere Suchbaeume”, binaer, weil jeder Branch (“Zweig”) zwei Unterbaeume hat und Suchbaum, weil wir die Baeume, die wir benutzen geordnet sein sollen, also zum Suchen und Finden von Werten (Daten) verwendet werden. Die Ordnung auf unserem Baum koennen wir folgendermassen beschreiben: Fuer jeden Zweig in unserem Baum gilt, dass alle Elemente, die im linken Unterbaum existieren kleiner als das im Zweig gespeicherte Element sind und alle Elemente im rechten Unterbaum sind grosser als das im Zweig gespeicherte Element. Dies ermoeglicht uns, ad hoc einen Algorithmus zu beschreiben, der beliebige vergleichbare(!!!) Elemente in unserem Baum zu finden.

<<tree-constructors>>

(define (lookup-integer tree n)
  (cond ((leaf? tree) #f)
        ((= n (branch-value tree)) #t)
        ((< n (branch-value tree))
         (lookup-integer (branch-left tree) n))
        (else
         (lookup-integer (branch-right tree) n))))

Es ist leicht zusehen, dass der gegebene Algorithmus rekursiv definiert ist. Wenn wir wissen wollen, ob eine Zahl in einem Leaf (“Blatt”) zu finden ist, dann antwortet der Algorithmus mit #f, da ein Leaf keine Werte enthaelt. Sollte die Funktionen einen Branch finden, dann ueberpruefen wir zu erst, ob das gesuchte Element im aktuellen Branch gespeichert ist, sollte das der Fall sein, geben wir #t zurueck. Sollte auch das nicht der Fall sein, wird ueberprueft, ob die gesuchte Zahl kleiner oder groesser ist, als das aktuelle Element (branch-value). Wenn das gesuchte Element nun also kleiner ist, wissen wir, dass es auf Grund der oben definierten Ordnung nur im linken Unterbaum stecken kann. Im “groesser”-Fall kann es wiederum nur im rechten Unterbaum stecken und wir setzen unsere Suche dort fort.

Es stellt sich nun die Frage, wie wir einen Baum konstruieren koennen, und es stellt sich heraus, dass die Konstruktion fast genauso wie die Suche ablaeuft. Wenn wir eine Element $x$ zu einem leeren Baum, also Leaf, hinzufuegen wollen, dass geben wir einen Branch zurueck, dessen branch-value gerade $x$ ist und dessen Unterbaeume leere sind. Sollten wir versuchen das Element zu einem Branch hinzuzufuegen, dann tritt einer der folgenden drei Faelle ein:

x = branch-value
Das Element ist bereits im Baum enthalten und wir geben einfach den Baum selbst zurueck.
x < branch-value
Um die Ordnung des Baumes zu bewahren, wissen wir, dass $x$ dem linken Unterbaum hinzugefuegt werden muss.
x < branch-value
Aequivalent zum obigen Fall muessen wir $x$ dem rechten Unterbaum hinzufuegen, um die Ordung zu wahren.
(define (insert-int tree x)
  (cond ((leaf? tree) (branch x leaf leaf))
        ((= x (branch-value tree)) tree)
        ((< x (branch-value tree))
         (branch (branch-value tree)
                 (insert-int (branch-left tree) x)
                 (branch-right tree)))
        (else
         (branch (branch-value tree)
                 (branch-left tree)
                 (insert-int (branch-right tree) x)))))

Damit haben wir die atomaren Operationen auf unserem Baum definiert. Die Funktionen die wir definiert haben sind aber auf integer-Werte spezialisiert, wir wuerden aber gern Baeume mit beliebigen vergleichbaren Elementen betrachten koennen. Dafuer brauchen wir eine Moeglichkeit Vergleiche zu kodieren. Wir waehlen dazu folgende Form:

Ein Vergleich ist eine Funktion mit zwei Parametern.

  1. Wenn diese Funktion $1$ zurueck gibt, ist das erste Argument groesser als das zweite Argument.
  2. Wenn die Funktion $0$ zurueck gibt, sind beide Argumente gleichwertig.
  3. Wenn die Funktion $-1$ zurueck gibt, ist das erste Argument groesser als zweite.

Die folgende Funktion “implementiert” eine Vergleichsfunktion fuer

(define (comp-int x y)
  (cond ((< x y) -1)
        ((> x y) 1)
        (else 0)))

Mit einer solchen Funktion koennen wir eine insert-Funktion definieren, unabhaengig von konkreten Vergleichsoperationen funktioniert.

(define (insert comp elem tree)
  (cond ((leaf? tree) (branch elem leaf leaf))
        (else
         (let* ((left (branch-left tree))
                (right (branch-right tree))
                (val (branch-value tree))
                (comp-val (comp elem val)))
           (cond ((= comp-val 1)
                  (branch val left (insert comp elem right)))
                 ((= comp-val -1)
                  (branch val (insert comp elem left) right))
                 ((= comp-val 0)
                  (branch elem left right))
                 (else (error "insert: supplied comparison funktion is illdefined"
                              comp)))))))

Die lookup-Funktion ist aequivalent.

(define (lookup comp elem tree)
  (cond ((leaf? tree) #f)
        (else
         (let* ((val (branch-value tree))
                (left (branch-left tree))
                (right (branch-right tree))
                (comp-val (comp elem val)))
           (cond ((= comp-val 0) val)
                 ((= comp-val 1)
                  (lookup comp elem left))
                 ((= comp-val -1)
                  (lookup comp elem right))
                 (else
                  (error "lookup: supplied comp function is ill defined "
                         comp)))))))

Wir koennen nun Werte einem Baum hinzufuegen und ueberpruefen, ob ein Wert im Baum gespeichert ist. Manchmal wollen wir aber auch einen Wert aus dem Baum loeschen. Loeschen soll an dieser Stelle konstruktiv verstanden werden. Wir wollen bestehende Baeume nicht modifizieren (in der Tat wissen wir noch gar nicht wie wir IRGENDEINEN Wert modifizieren koennen), sondern einen neuen Baum erschaffen, in dem alle Werte aus dem alten Baum vorhanden sind, bis auf den zu loeschenden Wert. Das Loeschen von Werten aus Baeumen ist ein wenig komplizierter als das Loeschen von Werten aus Listen (filter), da wir darauf achten muessen, dass die Struktur des Baumes erhalten bleibt. Die Strategie beim Loeschen ist folgendermassen:

Wenn wir Wert $x$ aus Baum $t$ loeschen wollen, suchen wir zu erst die Stelle an der $x$ im Baum steht. Falls $x ¬\in t$ geben wir einfach den urspruenglichen Baum zurueck. Falls $x ∈ t$, unterscheiden wir drei Faelle:

$x$ hat kein Kind
In diesem Fall ersetzen wir den Branch auf dem $x$ gespeichert ist mit einem Leaf.
$x$ hat ein Kind
In diesem Fall ersetzen wir $x$ durch sein Kind.
$x$ hat zwei Kinder
Wir tauschen $x$ mit dem “linkesten” Wert im rechten Unterbaum von $x$, und fuehren diesen Fall damit auf Fall 1 oder 2 zurueck.
(define (delete comp elem tree)
  ;; delete the leftest node of a tree and return a pair of the
  ;; deletet value and the new tree
  (define (delete-leftest t)
    (let ((left (branch-left t))
          (right (branch-right t))
          (val (branch-value t)))
      (cond ((leaf? left)
             (cons val
                   right))
            (else
             (let ((pair (delete-leftest left)))
               (cons (car pair)
                     (branch val (cdr pair) right)))))))
  (cond ((null? tree) leaf) ;; The value is not in the tree
        (else
         (let* ((left (branch-left tree))
                (val (branch-value tree))
                (right (branch-right tree))
                (comp-val (comp elem val)))
           (cond ((= 1 comp-val)
                  (branch val left (delete comp elem right)))
                 ((= -1 comp-val)
                  (branch val (delete comp elem left) right))
                 ((and (leaf? left) (leaf? right)) leaf)
                 ((leaf? right) left)
                 ((leaf? left) right)
                 (else
                  (let ((l (delete-leftest right)))
                    (branch (car l) left (cdr l)))))))))

Module

Guile/Scheme stellt ein Modulesystem bereit, auf das wir von nun an zurueck greifen wollen. Module werden mit dem Befehl use-modules geladen. Weitere Infos dazu findet ihr im Handbuch von Guile. Um z.B. die fold-Implementation fuer Listen zu benutzen, laden wir das Modul (srfi srfi-1).

(use-modules (srfi srfi-1))

(define (sum ls)
  (fold + 0 ls))

(sum '(1 2 3 4 5))

Eine Liste der verfuegbaren Standardmodule findet ihr auch im Handbuch.

Pattern matching

Es kann immer mal wieder vorkommen, dass wir Funktionen schreiben/benutzen, deren Rueckgabewerte oder Argumente umterschiedliche Strukturen haben koennen. Das einfachste Beispiel an dieser Stelle ist map fuer Listen.

(define (map fun l)
  (cond ((null? l) l)
        (else
         (cons (fun (car l))
               (map fun (cdr l))))))

Wir unterscheiden, ob das Argument l gerade #nil ist oder ein Paar. Dieses Muster kommt in funktionaler Programmierung relativ haeufig vor. Scheme bietet eine andere Moeglichkeit dieses Problem anzugehen: Pattern matching.

(use-modules (ice-9 match))

(define (map fun l)
  (match l
    (()
     '())
    ((head tail ...)
     (cons (fun head)
           (map fun tail)))))

Diese Implementation von map “ueberprueft” die Struktur von l. Handelt es sich um die leere Liste, wird diese leere Liste zurueck gegeben. An sonsten wird die Liste aufgeteilt in head und tail, wobei die Elemente der Liste an die entsprechenden Namen gebunden werden, fun wird auf head angewendet und das Ergebnis dann mit dem “gemapten” tail “geconst”.

Weitere Infos zum Thema “pattern matching” findest du im Handbuch von Guile.

Streams

Streams sind (so, wie Listen auch) Abstaktionen ueber Daten. Wir stellen uns Streams als (un-)endlich lange Sammlung von Daten vor, die wir nacheinander abrufen koennen. Ein Stream muss folgenden Gesetzmaessigkeiten gehorchen:

  • (car-stream (cons-stream a b)) = a
  • (cdr-stream (const-stream a b)) = b

Das sieht ja erstmal genauso wie die Definition einer Liste aus. Es gibt aber einen kleinen Unterschied zwischen Listen uns Streams: Streams berechnen ihr die enthaltenen Werte nur auf Abruf.

<<definitions-lists>>
<<definition-map>>

(define (print-and-mult-2 x)
  (display x)
  (newline)
  (* 2 x))

(head (map print-and-mult-2 (list 1 2 3 4)))

Functor

Ein Functor ist eine Abstraktion ueber Daten. Ein Functor ermoeglicht das Transformieren von Daten innerhalb einer Datenstruktur. Diese Transformationsfunktion wird haeufig map oder fmap genannt.

Diese fmap-Funktion muss ein paar Regeln gehorchen, um in die Kategorie Functor zu fallen.

  1. (fmap id x) = x
  2. (fmap g (fmap f x)) = (fmap (compose g f) x)

Die erste Functorregel sagt aus, dass wenn ich fmap mit der Indentitaet auf einen Wert anwende, dann soll der Wert selbst dabei herrauskommen. Die Idenitaet ist die Funktion, die Werte in sich selbst ueberfuert.

(define (id x) x)

Fuer Listen erwarten wir das intuitiv von der map-Funktion:

    (map id (list 1 2 3 4))
<-> (list (id 1) (id 2) (id 3) (id 4))
<-> (list 1 2 3 4)

Die zweite Functorregel sagt aus, dass es keine Rolle spielen darf, ob wir zweimal fmap auf einen Wert anwenden (erst f, dann g), oder ob wir die beiden Funktionen verketten und dann nur einmal fmap anwenden. Die compose Funktion ist folgendermasse definiert.

(define (compose g f)
  (lambda (x)
    (g (f x))))

Noch einmal am Beispiel von Listen:

    (map 2* (map 1+ (list 1 2 3)))
<-> (map 2* (list (1+ 1) (1+ 2) (1+ 3)))
<-> (list (2* (1+ 1)) (2* (1+ 2)) (2* (1+ 3)))
<-> (list ((compose 2* 1+) 1) ((compose 2* 1+) 2) ((compose 2* 1+) 3)
<-> (map (compose 2* 1+) (list 1 2 3))

Wir haben bereits Beispiele fuer Functors gesehen: Listen und Baeume. Dabei ist die map-Funktion fuer Listen auch die fmap-Funktion von Functors. Ein weiteres Beispiel fuer Functors ist der Datentyp maybe.

Beispiel: Maybe

Manchmal haben wir es mit Berechnungen zutun, die kein Ergebnis liefern. Ein Beispiel dafuer, waere die Funktion div, die 2 Zahlen a und b nimmt und das Ergebnis der Division a / b zurueck gibt.

Falls b gleich 0 ist, ist diese Operation nicht definiert. In vielen Programmiersprachen wird dieses Problem durch Exceptions behandelt, andere Sprachen geben im Fehlerfall einfach den 0-Zeiger zurueck und setzen eine Fehlervariable auf eine speziellen Fehlercode.

Wir wollen uns einmal anschauen, wie wir eine potentiell ergebnislose Operation ohne diese Spezialwerkzeuge behandeln koennen. Dazu benoetigen wir eine Art Behaelter, der entweder nichts (nothing) oder genau einen Wert (just VALUE) enthalten kann. Wir muessen irgendwie Werte in diesen Behaelter tun koenne und auch wieder extrahieren koennen. Dazu definieren wir folgende Regeln:

  • (nothing? nothing) = #t
  • (nothing? (just x)) = #f
  • (just? nothing) = #f
  • (just? (just x)) = #t
  • (maybe x nothing) = x
  • (maybe x (just y)) = y
  • (from-just nothing) = UNDEFINED
  • (from-just (just x)) = x

Wir koennen mit diesen Operationen nun die div-Funktion definieren:

(define (div x y)
  (if (equal? y 0)
      nothing
      (just (/ x y))))

Stellen wir uns nun vor, dass wir das Ergebnis weiter verwenden wollen, in dem wir eine Funktion definieren, die eine Zahl das Reziproke einer Zahl findet (d.h. 1/x) und dann 1 hinzu addiert.

(define (rezi-and-add n)
  (let ((rezi (div 1 n)))
    (if (nothing? rezi)
        nothing
        (just (+ 1 (from-just rezi))))))

Diese erste Implementation sieht irgendwie umstaendlich aus. Wir entpacken das Ergebnis der division nur, um es danach wieder zu verpacken, ausserdem muessen wir uns explizit um den Fehlerfall kuemmern. Was wir brauchen, ist eine Funktion, die einen maybe-Wert nimmt und eine Operation darauf anwendet, sofern der Wert nicht leer ist. Wir nennen diese Funktion mmap (MaybeMAP). Diese Funktion soll die folgenden Regeln befolgen:

  • (mmap f nothing) = nothing
  • (mmap f (just x)) = (just (f x))

Mithilfe dieser Funktion koennen wir die rezi-and-add-Funktion viel einfacher implementieren:

(define (rezi-and-add n)
  (mmap 1+ (div 1 n)))

Diese zweite verbesserte Version hat die selbe Funktionalitaet ohne, dass wir uns explizit um den Fehlerfall kuemmern muessen.

Implementieren wir nun die interne Repraesentation unseres neuen Datentyps.

(define (just val)
  (cons 'just
        val))

(define nothing
  (cons 'nothing #nil))

(define (just? mval)
  (equal? (car mval) 'just))

(define (nothing? mval)
  (equal? (car mval) 'nothing))

(define (maybe alt mval)
  (if (just? mval)
      (cdr mval)
      alt))

(define (from-just mval)
  (if (just? mval)
      (cdr mval)
      (error "FROM-JUST: cannot extract value from nothing" mval)))

(define (mmap fun mval)
  (if (just? mval)
      (just (fun (from-just mval)))
      nothing))

Wir koennen nun eine kleine Functorbibliothek erstellen. Ziel ist es, eine fmap-Funktion zu schreiben, die auf Listen, Maybewerten, Baeumen und eventuell noch anderen Datenstrukturen operieren kann.

Data directed programming

Stellen wir uns vor, dass wir eine Bibliothek schreiben wollen, die ein einheitliches Interface fuer den Umgang mit Baeumen, Listen und Doppellisten ermoeglichen soll. Wir koennen zum Beispiel fuer alle drei Datentypen map und foldl definieren. Falls wir es schaffen, eine einzige foldl Funktion zu schreiben, bekommen wir automatisch eine length-Funktion “for free”. Um dies zu bewerkstelligen, muessen wir irgendwie entscheiden koennen, mit welchem Datentyp wir es zu tun haben. Dazu nutzen “type tags”.

Tagged data

(define (attach-tag type-tag value)
  (cons type-tag value))

(define (get-tagged-type val)
  (cond ((pair? val) (car val))
        (else (error "GET-TAGGED-TYPE: not a tagged value" val))))

(define (get-tagged-value val)
  (cond ((pair? val) (cdr val))
        (else (error "GET-TAGGED-VALUE: not a tagged value" val))))

Jetzt muessen wir neue Konstruktoren fuer unsere Datentypen definieren, die unsere type tags benutzen.

(define (get-list-internal xs)
  (if (eq? (get-tagged-type xs) 'list)
      (get-tagged-value xs)
      (error "GET-LIST-INTERNAL: value is not a list" xs)))

(define (tail xs)
  (cdr (get-list-internal xs)))

(define (head xs)
  (car (get-list-internal xs)))

(define empty-list
  (attach-tag 'list #nil))

(define (empty? l)
  (null? (get-list-internal l)))

(define (prepend x xs)
  (attach-tag 'list
              (cons x
                    xs)))

(define (init xs)
  (cond ((empty? xs) (error "INIT: list is empty" xs))
        ((empty? (tail xs)) empty-list)
        (else (prepend (head xs)
                       (init (tail xs))))))

(define (last xs)
  (cond ((empty? xs) (error "LAST: list is empty" xs))
        ((empty? (tail xs)) (head xs))
        (else (last (tail xs)))))

(define (reverse xs)
  (define (iter accu rest)
    (cond ((empty? rest) accu)
          (else (iter (prepend (head rest) accu)
                      (tail rest)))))
  (iter empty-list xs))

(define (tagged-list . xs)
  (define (iter ls)
    (cond ((null? ls) empty-list)
          (else (prepend (car ls) (iter (cdr ls))))))
  (iter xs))
(define leaf
  (attach-tag 'tree 'leaf))

(define (branch value left right)
  (attach-tag 'tree
              (list value left right)))

(define (get-tree-internal t)
  (if (eq? (get-tagged-type t) 'tree)
      (get-tagged-value t)
      (error "GET-LEAF-INTERNAL: value is not a tree" t)))

(define (leaf? t)
  (eq? t leaf))

(define (branch? t)
  (not (leaf? t)))

(define (left t)
  (if (branch? t)
      (cadr (get-tree-internal t))
      (error "LEFT: tree is a leaf" t)))

(define (right t)
  (if (branch? t)
      (caddr (get-tree-internal t))
      (error "RIGHT: tree is a leaf" t)))

(define (branch-value t)
  (if (branch? t)
      (car (get-tree-internal t))
      (error "BRANCH-VALUE: tree is a leaf" t)))

(define (tree-add-elem smaller-or-equal t x)
  (if (leaf? t)
      (branch x leaf leaf)
      (if (smaller-or-equal x (branch-value t))
          (branch (branch-value t)
                  (tree-add-elem smaller-or-equal
                                 (left t)
                                 x)
                  (right t))
          (branch (branch-value t)
                  (left t)
                  (tree-add-elem smaller-or-equal
                                 (right t)
                                 x)))))

(define (tree-from-list smaller-or-equal xs)
  (define (iter accu rest)
    (if (empty? rest)
        accu
        (iter (tree-add-elem smaller-or-equal accu (head rest))
              (tail rest))))
  (iter leaf xs))
(define empty-dlist
  (attach-tag 'dlist
              (list empty-list empty-list)))

(define (get-dlist-internal dl)
  (if (eq? (get-tagged-type dl) 'dlist)
      (get-tagged-value dl)
      (error "GET-DLIST-INTERNAL: value is not a double list" dl)))

(define (dprepend val dl)
  (let* ((intern (get-dlist-internal dl))
         (front (car intern))
         (back (cadr intern)))
    (attach-tag 'dlist
                  (list (prepend val front)
                        back))))

(define (dappend dl val)
  (let* ((intern (get-dlist-internal dl))
         (front (car intern))
         (back (cadr intern)))
    (attach-tag 'dlist
                  (list front
                        (prepend val back)))))

(define (dtail dl)
  (let* ((intern (get-dlist-internal dl))
         (front (car intern))
         (back (cadr intern)))
    (cond ((not (empty? front)) (attach-tag 'dlist
                                            (cons (tail front)
                                                  back)))
          (else (attach-tag 'dlist
                            (cons (tail (reverse back))
                                  empty-list))))))

(define (dinit dl)
  (let* ((intern (get-dlist-internal dl))
         (front (car intern))
         (back (cadr intern)))
    (cond ((not (empty? back)) (attach-tag 'dlist
                                           (cons front
                                                 (tail back))))
          (else (attach-tag 'dlist
                            (cons empty-list
                                  (tail (reverse front))))))))

(define (dhead dl)
  (let* ((intern (get-dlist-internal dl))
         (front (car intern))
         (back (cadr intern)))
    (cond ((not (empty? front)) (head front))
          (else (last back)))))

(define (dlast dl)
  (let* ((intern (get-dlist-internal dl))
         (front (car intern))
         (back (cadr intern)))
    (cond ((not (empty? back)) (head back))
          (else (last front)))))

Nun koennen wir eine entsprechende map-Funktion definieren.

<<definition-type-tags>>
<<definition-tagged-list>>
<<definition-tagged-tree>>
<<definition-tagged-double-list>>

(define (map fun container)
  (define (lmap fun l)
    (if (empty? l)
        empty-list
        (prepend (fun (head l))
                 (lmap fun (tail l)))))
  (define (tmap fun t)
    (if (leaf? t)
        leaf
        (branch (fun (branch-value t))
                (tmap fun (left t))
                (tmap fun (right t)))))
  (define (dmap fun dl)
    (let* ((intern (get-dlist-internal dl))
           (front (car intern))
           (back (cadr intern)))
      (attach-tag 'dlist
                  (cons (lmap fun front)
                        (lmap fun back)))))
  (cond ((eq? (get-tagged-type container) 'list) (lmap fun container))
        ((eq? (get-tagged-type container) 'tree) (tmap fun container))
        ((eq? (get-tagged-type container) 'dlist) (dmap fun container))
        (else (error "MAP: unknown type" container))))

Diese Definition ermoeglicht nun ein einheitliches “Interface” fuer alle Datentypen, die die map-Operation unterstuetzen. Ein entscheidender Nachteil ist dabei jedoch, dass wir jedesmal, wenn wir einen neuen Datentypen hinzufuegen wollen, die map-Funktion selbst editieren muessen. Das fuehrt dazu, dass unser kleines Modul nicht gut erweitert werden kann.

Type lookup tables

Ein modularerer Ansatz fuer “ueberladene Funktionen” ist das Benutzen einer Tabelle, in der hinterlegt ist, welche Funktion fuer welchen Typen benutzt werden soll. Wir koennen uns eine Tabelle folgendermassen vorstellen:

Operationlisttreedlist
maplmaptmapdmap
foldllfoldltfoldldfoldl

Wenn ein neuer Datentype geschrieben wird, der map unterstuetzt, muss er einfach nur der Tabelle hinzugefuegt werden.

<<definition-type-tags>>
<<definition-tagged-list>>
<<definition-tagged-tree>>
<<definition-tagged-double-list>>

(define map-lookup-table (make-hash-table))

(define (map fun container)
  (let ((op (hashq-ref map-lookup-table
                       (get-tagged-type container))))
    (if op
        (op fun container)
        (error "MAP: not defined for this type"
               (get-tagged-type container)))))

(define (mmap fun l)
  (if (empty? l)
      empty-list
      (prepend (fun (head l)) (mmap fun (tail l)))))

(define (tmap fun t)
  (if (leaf? t)
      leaf
      (branch (fun (branch-value t))
              (tmap fun (left t))
              (tmap fun (right t)))))

(define (dmap fun dl)
  (let* ((intern (get-dlist-internal dl))
         (front (car intern))
         (back (cadr intern)))
    (attach-tag 'dlist
                (list (map fun front)
                      (map fun back)))))

(hashq-set! map-lookup-table 'list mmap)
(hashq-set! map-lookup-table 'dlist dmap)
(hashq-set! map-lookup-table 'tree tmap)

Veraenderliche Daten

Im obigen Programmbeispiel wird der Befehl hashq-set! verwendet. Dieser Befehl nimmt eine Hashmap entgegen und nimmt ein Update vor. Hier sollten wir ein wenig stutzig werden, da bisher einmal erzeugte Daten nicht mehr veraendert werden konnten, mit anderen Worten: hashq-set! ist eine Zuweisungsanweisung.

Wir sollten uns darueber im Klaren sein, sein, dass wir sehr vorsichtig sein muessen, wenn wir veraenderliche Daten haben, da viele nette Eigenschaften funktionaler Programmierung verloren gehen, wenn wir Zuweisungen erlauben. Darunter faellt unter anderem, dass wir nicht mehr davon ausgehen koennen, dass eine Funktion, aufgerufen mit bestimmten Argumenten, immer das selbe Ergebnis produziert, egal wo sie im Programm aufgerufen wird.

Im weiteren Verlauf dieses Textes werden wir Zuweisungen nur fuer 2 Dingen verwenden: “data directed programming” und “memoization” (das werden wir noch kennenlernen). Dies wird den imperativen Einfluss auf unsere Programme gering halten, aber trotzdem flexibel genug sein, um spannende Konzepte kennenzulernen.

GOOPS

Guile Scheme stellt bereits eine Infrastruktur fuer genau solche Anwendungsfaelle bereit, GOOPS. Der Name leitet sich (irgendwie) von objektorientierter Programmierung ab.

Wenn wir GOOPS benutzen wollen, muessen wir es ersteinmal importieren:

(use-modules (oop goops))

Um jetzt unser Funktorinterface zu implementieren, muessen wir eine generische Funktion definieren, die die “mapping”-Funktion repraesentiert

(define-generic fmap)

Nun erstellen wir eine Klasse, wobei Klasse hier sehr nah dran ist, an der Javadefinition fuer Klasse.

(define-class <maybe> ()
  (is-set #:getter maybe-is-set
          #:init-keyword #:is-set)
  (value #:getter maybe-value
         #:init-keyword #:value))

Wir wollen nun Konstruktoren definieren. Der primitive Konstruktor, den jede Klasses von Hause aus besitzt, ist make. Wir wollen aber eigene Konstruktoren definieren.

(define (make-just val)
  (make <maybe> #:is-set #t #:value val))
(define nothing
  (make <maybe> #:is-set #f #:value #f))

Wir haben jetzt die Datenfelder und die Konstruktoren von <maybe> definiert, aber noch keine Methoden. Anders als in Java und Co. werden Methoden nicht als Attribute an Klassen angehaengt, sonder ueber Funktionssignatur deklariert.

(define-method (is-just (x <maybe>))
  (maybe-is-set x))

Die is-nothing-Funktion ist trivial, da wir einfach nur das Ergebnis von is-just negieren muessen.

(define-method (is-nothing (x <maybe>))
  (not (is-just x)))

Unsere fmap-Funktion wird nun auch als Methode von <maybe> implementiert.

(define-method (fmap fun (x <maybe>))
  (if (maybe-is-set x)
      (make-just (fun (maybe-value x)))
      x))

Nun muessen wir nur noch alles zu einem Modul zusammenfassen:

<<goops-example-imports>>
<<goops-example-defs>>
<<goops-example-maybe-def>>
<<goops-example-maybe-methods>>

Objektorientierung

Objektorientierung ist eine Spezialform von data directed programming. Die Methoden, fuer ein Datensatz ein gueltiges Argument darstellen, werden oft Objektmethoden genannt. In den meisten objektorientierten Sprachen werden diese Methoden so definiert, dass ein Parameter der Methode das Objekt ist, “auf das die Methode angewendet” werden soll. In Python geschieht das durch das Argument “self”, in Java und Javascript ist es “this”. Aus dieser Notation ergeben sich haeufig seltsame Streitigkeiten, welche Method zu welcher Objektklasse gehoert.

Nehmen wir einmal an, wir haben eine Klasse Form, die geometrische Formen abbildet und eine Klasse Leinwand, auf die diese Formen “gemalt” werden koennen. Ist die Methode “male Form auf Leinwand” nun eine Methode von Form, die als Argument eine Leinwand entgegen nimmt, oder eine Methode der Klasse Leinwand, die eine Form entgegen nimmt. Man findet Vertreter der eine, als auch der anderen Interpretation.

Dieses Problem ist ein guter Hinweis darauf, dass die Implementation von “OO” durch implizite Argumente und “Klassenmethoden”, die als Standard gilt, so ihre Problemchen hat, ueber die man sich im klaren seien sollte. Das heisst nicht, dass die Art und Weise, wie Java und Co. OO implementieren, schlecht oder abzulehnen ist, sondern soll nur ein Hinweis auf Alternativen und Probleme sein.

Shared variables

Mit der methode set! kann der Wert einer Variable veraendert werden.

(define n 0)
(set! n 1)
(display n)
;; 1

Um die selbe Variable an verschiedenen stellen in unserem Program veraendern zu koennen, nutzen wir set!.

;; Dieses Programm soll es ermoeglichen, einen zentralen Kontostand zu
;; verwalten

;; Wir benoetigen zwei funktionen: einzahlen und kontostand.
(define deposit #f) ;; einzahlen
(define get-balance #f) ;; kontostand

(let ((balance 0)) ;; der kontostand wird mit 0 initialisiert
  (set! get-balance
    (lambda () balance))
  (set! desposit
    (lambda (amount)
      (set! balance (+ balance amount)))))

(get-balance) ;; 0
(deposit 5)
(get-balance) ;; 5
(deposit 10)
(get-balance) ;; 15

Beispiel: web server

Wie die meisten modernen Sprachen, stellt GNU Guile eine Webserverimplementation bereit, mit der beliebige Daten zwischen Client und Server ausgetauscht werden koennen. Der entsprechende Code ist im Modul web server zu finden.

(use-modules (web server))

Das Modul stellt eine Funktion run-server bereit, die den Webserver startet. run-server nimmt ein Pflichtargument entgegen, welches eine Funktion ist, wir nennen sie handler.

(run-server handler)

Die handler-Funktion bearbeitet eingehende Requests und konstruiert (gueltige) Responses. Sie muss zwei Parameter verstehen, wobei der erste Parameter den Request Header und der zweite Parameter den Request body repraesentiert.

(define (handler request-header request-body)
  <<server-handler-implementation>>)

Um dem Interface des Webservers zu genuegen, muss die Funktion ausserdem zwei Argumente zurueck geben, nicht zu verwechseln mit einem Paar von zwei Werten. Guile Scheme stellt die moeglichkeit bereit, dass eine Funktion mehrere Rueckgabewerte hat, dies wird ueber die values-Funktion realisiert, die zu den Grundfunktionen der Sprache gehoert. Der erste Rueckgabewert repraesentiert den Response Header und der zweite Rueckgabewert den Response Body.

(values
 <<server-response-header-implementation>>
 <<server-response-body-implementation>>)

Um den Response Header zu konstruieren, kann die build-response-Funktion aus dem Modul web response genutzt werden.

(use-modules (web response))
(build-response #:code 200
                #:headers `((content-type . (text/html))))

Die Implementation fuer den Response Body muss eine Funktion mit einem Argument sein. Dieses Argument ist ein sogenannter Port, auf den die Ausgabe geschrieben werden muss. Dazu kann zum Beispiel die display-Funktion benutzt werden.

(lambda (port)
  (display "hello world" port))

Das vollstaendige Programm besteht dann aus den oben definierten Bestandteilen.

<<server-imports>>

<<server-handler-def>>

<<server-run>>

Wenn das Programm examples/server1.scm ausgefuehrt wird, wird ein Webserver auf Port 8080 gestartet. Bei einem Request liefert der Server ein HTML-Dokumen aus, in dem “hello world” steht.

Quoting

LISP eignet sich sehr gut fuer sogenanntes “meta programming”, damit bezeichnet man das Schreiben und Manipulieren von Computerprogrammen durch Programme. Jeder gueltige LISP-Ausdruck laesst sich als Liste darstellen.

(define exp (list '+ 1 2))
(eval exp (interaction-environment))

Zum evaluieren des Codes muss der Interpreter wissen, in welcher Umgebung er den Code ausfuehren soll. Die Funktion interaction-environment definiert eine Umgebung aehnlich zu der Umgebung in der REPL.

GNU Free Documentation License

GNU Free Documentation License Version 1.3, 3 November 2008

Copyright (C) 2000, 2001, 2002, 2007, 2008 Free Software Foundation, Inc. http://fsf.org/ Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.

PREAMBLE

The purpose of this License is to make a manual, textbook, or other functional and useful document “free” in the sense of freedom: to assure everyone the effective freedom to copy and redistribute it, with or without modifying it, either commercially or noncommercially. Secondarily, this License preserves for the author and publisher a way to get credit for their work, while not being considered responsible for modifications made by others.

This License is a kind of “copyleft”, which means that derivative works of the document must themselves be free in the same sense. It complements the GNU General Public License, which is a copyleft license designed for free software.

We have designed this License in order to use it for manuals for free software, because free software needs free documentation: a free program should come with manuals providing the same freedoms that the software does. But this License is not limited to software manuals; it can be used for any textual work, regardless of subject matter or whether it is published as a printed book. We recommend this License principally for works whose purpose is instruction or reference.

APPLICABILITY AND DEFINITIONS

This License applies to any manual or other work, in any medium, that contains a notice placed by the copyright holder saying it can be distributed under the terms of this License. Such a notice grants a world-wide, royalty-free license, unlimited in duration, to use that work under the conditions stated herein. The “Document”, below, refers to any such manual or work. Any member of the public is a licensee, and is addressed as “you”. You accept the license if you copy, modify or distribute the work in a way requiring permission under copyright law.

A “Modified Version” of the Document means any work containing the Document or a portion of it, either copied verbatim, or with modifications and/or translated into another language.

A “Secondary Section” is a named appendix or a front-matter section of the Document that deals exclusively with the relationship of the publishers or authors of the Document to the Document’s overall subject (or to related matters) and contains nothing that could fall directly within that overall subject. (Thus, if the Document is in part a textbook of mathematics, a Secondary Section may not explain any mathematics.) The relationship could be a matter of historical connection with the subject or with related matters, or of legal, commercial, philosophical, ethical or political position regarding them.

The “Invariant Sections” are certain Secondary Sections whose titles are designated, as being those of Invariant Sections, in the notice that says that the Document is released under this License. If a section does not fit the above definition of Secondary then it is not allowed to be designated as Invariant. The Document may contain zero Invariant Sections. If the Document does not identify any Invariant Sections then there are none.

The “Cover Texts” are certain short passages of text that are listed, as Front-Cover Texts or Back-Cover Texts, in the notice that says that the Document is released under this License. A Front-Cover Text may be at most 5 words, and a Back-Cover Text may be at most 25 words.

A “Transparent” copy of the Document means a machine-readable copy, represented in a format whose specification is available to the general public, that is suitable for revising the document straightforwardly with generic text editors or (for images composed of pixels) generic paint programs or (for drawings) some widely available drawing editor, and that is suitable for input to text formatters or for automatic translation to a variety of formats suitable for input to text formatters. A copy made in an otherwise Transparent file format whose markup, or absence of markup, has been arranged to thwart or discourage subsequent modification by readers is not Transparent. An image format is not Transparent if used for any substantial amount of text. A copy that is not “Transparent” is called “Opaque”.

Examples of suitable formats for Transparent copies include plain ASCII without markup, Texinfo input format, LaTeX input format, SGML or XML using a publicly available DTD, and standard-conforming simple HTML, PostScript or PDF designed for human modification. Examples of transparent image formats include PNG, XCF and JPG. Opaque formats include proprietary formats that can be read and edited only by proprietary word processors, SGML or XML for which the DTD and/or processing tools are not generally available, and the machine-generated HTML, PostScript or PDF produced by some word processors for output purposes only.

The “Title Page” means, for a printed book, the title page itself, plus such following pages as are needed to hold, legibly, the material this License requires to appear in the title page. For works in formats which do not have any title page as such, “Title Page” means the text near the most prominent appearance of the work’s title, preceding the beginning of the body of the text.

The “publisher” means any person or entity that distributes copies of the Document to the public.

A section “Entitled XYZ” means a named subunit of the Document whose title either is precisely XYZ or contains XYZ in parentheses following text that translates XYZ in another language. (Here XYZ stands for a specific section name mentioned below, such as “Acknowledgements”, “Dedications”, “Endorsements”, or “History”.) To “Preserve the Title” of such a section when you modify the Document means that it remains a section “Entitled XYZ” according to this definition.

The Document may include Warranty Disclaimers next to the notice which states that this License applies to the Document. These Warranty Disclaimers are considered to be included by reference in this License, but only as regards disclaiming warranties: any other implication that these Warranty Disclaimers may have is void and has no effect on the meaning of this License.

VERBATIM COPYING

You may copy and distribute the Document in any medium, either commercially or noncommercially, provided that this License, the copyright notices, and the license notice saying this License applies to the Document are reproduced in all copies, and that you add no other conditions whatsoever to those of this License. You may not use technical measures to obstruct or control the reading or further copying of the copies you make or distribute. However, you may accept compensation in exchange for copies. If you distribute a large enough number of copies you must also follow the conditions in section 3.

You may also lend copies, under the same conditions stated above, and you may publicly display copies.

COPYING IN QUANTITY

If you publish printed copies (or copies in media that commonly have printed covers) of the Document, numbering more than 100, and the Document’s license notice requires Cover Texts, you must enclose the copies in covers that carry, clearly and legibly, all these Cover Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on the back cover. Both covers must also clearly and legibly identify you as the publisher of these copies. The front cover must present the full title with all words of the title equally prominent and visible. You may add other material on the covers in addition. Copying with changes limited to the covers, as long as they preserve the title of the Document and satisfy these conditions, can be treated as verbatim copying in other respects.

If the required texts for either cover are too voluminous to fit legibly, you should put the first ones listed (as many as fit reasonably) on the actual cover, and continue the rest onto adjacent pages.

If you publish or distribute Opaque copies of the Document numbering more than 100, you must either include a machine-readable Transparent copy along with each Opaque copy, or state in or with each Opaque copy a computer-network location from which the general network-using public has access to download using public-standard network protocols a complete Transparent copy of the Document, free of added material. If you use the latter option, you must take reasonably prudent steps, when you begin distribution of Opaque copies in quantity, to ensure that this Transparent copy will remain thus accessible at the stated location until at least one year after the last time you distribute an Opaque copy (directly or through your agents or retailers) of that edition to the public.

It is requested, but not required, that you contact the authors of the Document well before redistributing any large number of copies, to give them a chance to provide you with an updated version of the Document.

MODIFICATIONS

You may copy and distribute a Modified Version of the Document under the conditions of sections 2 and 3 above, provided that you release the Modified Version under precisely this License, with the Modified Version filling the role of the Document, thus licensing distribution and modification of the Modified Version to whoever possesses a copy of it. In addition, you must do these things in the Modified Version:

A.
Use in the Title Page (and on the covers, if any) a title distinct from that of the Document, and from those of previous versions (which should, if there were any, be listed in the History section of the Document). You may use the same title as a previous version if the original publisher of that version gives permission.
B.
List on the Title Page, as authors, one or more persons or entities responsible for authorship of the modifications in the Modified Version, together with at least five of the principal authors of the Document (all of its principal authors, if it has fewer than five), unless they release you from this requirement.
C.
State on the Title page the name of the publisher of the Modified Version, as the publisher.
D.
Preserve all the copyright notices of the Document.
E.
Add an appropriate copyright notice for your modifications adjacent to the other copyright notices.
F.
Include, immediately after the copyright notices, a license notice giving the public permission to use the Modified Version under the terms of this License, in the form shown in the Addendum below.
G.
Preserve in that license notice the full lists of Invariant Sections and required Cover Texts given in the Document’s license notice.
H.
Include an unaltered copy of this License.
I.
Preserve the section Entitled “History”, Preserve its Title, and add to it an item stating at least the title, year, new authors, and publisher of the Modified Version as given on the Title Page. If there is no section Entitled “History” in the Document, create one stating the title, year, authors, and publisher of the Document as given on its Title Page, then add an item describing the Modified Version as stated in the previous sentence.
J.
Preserve the network location, if any, given in the Document for public access to a Transparent copy of the Document, and likewise the network locations given in the Document for previous versions it was based on. These may be placed in the “History” section. You may omit a network location for a work that was published at least four years before the Document itself, or if the original publisher of the version it refers to gives permission.
K.
For any section Entitled “Acknowledgements” or “Dedications”, Preserve the Title of the section, and preserve in the section all the substance and tone of each of the contributor acknowledgements and/or dedications given therein.
L.
Preserve all the Invariant Sections of the Document, unaltered in their text and in their titles. Section numbers or the equivalent are not considered part of the section titles.
M.
Delete any section Entitled “Endorsements”. Such a section may not be included in the Modified Version.
N.
Do not retitle any existing section to be Entitled “Endorsements” or to conflict in title with any Invariant Section.
O.
Preserve any Warranty Disclaimers.

If the Modified Version includes new front-matter sections or appendices that qualify as Secondary Sections and contain no material copied from the Document, you may at your option designate some or all of these sections as invariant. To do this, add their titles to the list of Invariant Sections in the Modified Version’s license notice. These titles must be distinct from any other section titles.

You may add a section Entitled “Endorsements”, provided it contains nothing but endorsements of your Modified Version by various parties–for example, statements of peer review or that the text has been approved by an organization as the authoritative definition of a standard.

You may add a passage of up to five words as a Front-Cover Text, and a passage of up to 25 words as a Back-Cover Text, to the end of the list of Cover Texts in the Modified Version. Only one passage of Front-Cover Text and one of Back-Cover Text may be added by (or through arrangements made by) any one entity. If the Document already includes a cover text for the same cover, previously added by you or by arrangement made by the same entity you are acting on behalf of, you may not add another; but you may replace the old one, on explicit permission from the previous publisher that added the old one.

The author(s) and publisher(s) of the Document do not by this License give permission to use their names for publicity for or to assert or imply endorsement of any Modified Version.

COMBINING DOCUMENTS

You may combine the Document with other documents released under this License, under the terms defined in section 4 above for modified versions, provided that you include in the combination all of the Invariant Sections of all of the original documents, unmodified, and list them all as Invariant Sections of your combined work in its license notice, and that you preserve all their Warranty Disclaimers.

The combined work need only contain one copy of this License, and multiple identical Invariant Sections may be replaced with a single copy. If there are multiple Invariant Sections with the same name but different contents, make the title of each such section unique by adding at the end of it, in parentheses, the name of the original author or publisher of that section if known, or else a unique number. Make the same adjustment to the section titles in the list of Invariant Sections in the license notice of the combined work.

In the combination, you must combine any sections Entitled “History” in the various original documents, forming one section Entitled “History”; likewise combine any sections Entitled “Acknowledgements”, and any sections Entitled “Dedications”. You must delete all sections Entitled “Endorsements”.

COLLECTIONS OF DOCUMENTS

You may make a collection consisting of the Document and other documents released under this License, and replace the individual copies of this License in the various documents with a single copy that is included in the collection, provided that you follow the rules of this License for verbatim copying of each of the documents in all other respects.

You may extract a single document from such a collection, and distribute it individually under this License, provided you insert a copy of this License into the extracted document, and follow this License in all other respects regarding verbatim copying of that document.

AGGREGATION WITH INDEPENDENT WORKS

A compilation of the Document or its derivatives with other separate and independent documents or works, in or on a volume of a storage or distribution medium, is called an “aggregate” if the copyright resulting from the compilation is not used to limit the legal rights of the compilation’s users beyond what the individual works permit. When the Document is included in an aggregate, this License does not apply to the other works in the aggregate which are not themselves derivative works of the Document.

If the Cover Text requirement of section 3 is applicable to these copies of the Document, then if the Document is less than one half of the entire aggregate, the Document’s Cover Texts may be placed on covers that bracket the Document within the aggregate, or the electronic equivalent of covers if the Document is in electronic form. Otherwise they must appear on printed covers that bracket the whole aggregate.

TRANSLATION

Translation is considered a kind of modification, so you may distribute translations of the Document under the terms of section 4. Replacing Invariant Sections with translations requires special permission from their copyright holders, but you may include translations of some or all Invariant Sections in addition to the original versions of these Invariant Sections. You may include a translation of this License, and all the license notices in the Document, and any Warranty Disclaimers, provided that you also include the original English version of this License and the original versions of those notices and disclaimers. In case of a disagreement between the translation and the original version of this License or a notice or disclaimer, the original version will prevail.

If a section in the Document is Entitled “Acknowledgements”, “Dedications”, or “History”, the requirement (section 4) to Preserve its Title (section 1) will typically require changing the actual title.

TERMINATION

You may not copy, modify, sublicense, or distribute the Document except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, or distribute it is void, and will automatically terminate your rights under this License.

However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation.

Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice.

Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, receipt of a copy of some or all of the same material does not give you any rights to use it.

FUTURE REVISIONS OF THIS LICENSE

The Free Software Foundation may publish new, revised versions of the GNU Free Documentation License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. See http://www.gnu.org/copyleft/.

Each version of the License is given a distinguishing version number. If the Document specifies that a particular numbered version of this License “or any later version” applies to it, you have the option of following the terms and conditions either of that specified version or of any later version that has been published (not as a draft) by the Free Software Foundation. If the Document does not specify a version number of this License, you may choose any version ever published (not as a draft) by the Free Software Foundation. If the Document specifies that a proxy can decide which future versions of this License can be used, that proxy’s public statement of acceptance of a version permanently authorizes you to choose that version for the Document.

RELICENSING

“Massive Multiauthor Collaboration Site” (or “MMC Site”) means any World Wide Web server that publishes copyrightable works and also provides prominent facilities for anybody to edit those works. A public wiki that anybody can edit is an example of such a server. A “Massive Multiauthor Collaboration” (or “MMC”) contained in the site means any set of copyrightable works thus published on the MMC site.

“CC-BY-SA” means the Creative Commons Attribution-Share Alike 3.0 license published by Creative Commons Corporation, a not-for-profit corporation with a principal place of business in San Francisco, California, as well as future copyleft versions of that license published by that same organization.

“Incorporate” means to publish or republish a Document, in whole or in part, as part of another Document.

An MMC is “eligible for relicensing” if it is licensed under this License, and if all works that were first published under this License somewhere other than this MMC, and subsequently incorporated in whole or in part into the MMC, (1) had no cover texts or invariant sections, and (2) were thus incorporated prior to November 1, 2008.

The operator of an MMC Site may republish an MMC contained in the site under CC-BY-SA on the same site at any time before August 1, 2009, provided the MMC is eligible for relicensing.

ADDENDUM: How to use this License for your documents

To use this License in a document you have written, include a copy of the License in the document and put the following copyright and license notices just after the title page:

Copyright (c)  YEAR  YOUR NAME.
Permission is granted to copy, distribute and/or modify this document
under the terms of the GNU Free Documentation License, Version 1.3
or any later version published by the Free Software Foundation;
with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
A copy of the license is included in the section entitled "GNU
Free Documentation License".

If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, replace the “with…Texts.” line with this:

with the Invariant Sections being LIST THEIR TITLES, with the
Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST.

If you have Invariant Sections without Cover Texts, or some other combination of the three, merge those two alternatives to suit the situation.

If your document contains nontrivial examples of program code, we recommend releasing these examples in parallel under your choice of free software license, such as the GNU General Public License, to permit their use in free software.

About

Materials for a LISP class (in german)

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published