Archiv für die Kategorie „Scheme“

Scheme: Funktion mit internem privatem Attribut (private Variable)

Sonntag, 21. März 2010

Mit folgender Anweisung definiert man eine simple Zählfunktion in Scheme von der man beliebig viele unabhänge Zähler parallel erstellen kann.

(define (counter init)
  (local
    ((define i (- init 1)))
    (lambda ()
      (begin
        (set! i (+ i 1))
        i
      )
    )
  )
)

Wie man sieht definiert man zuerst eine Funktion namens counter die einen Parameter erwartet. Die Funktion hat intern eine lokale Variable die zu beginn auf den übergebenen Wert (vermindert um 1) gesetzt wird. Schließlich gibt die Funktion eine neue Funktion zurück die mit der eben erstellen lokalen Variable zählt. Diese interne private Variable kommt einem privaten Attribut einer Klasse einer modernen Programmiersprache nahe.
Somit ergeben folgende Anweisungen:

(define c1 (counter 0))
(define c2 (counter 1100))
(c1)
(c2)
(c2)
(c1)
(c1)
(c1)
(c2)
0
1100
1101
1
2
3
1102

Quicksort in Scheme

Mittwoch, 17. März 2010
(define (my-quicksort l)
  (if (empty? l)
      empty
      (append
       (my-quicksort (filter (lambda (x) (<= x (first l))) (rest l)))
       (list (first l))
       (my-quicksort (filter (lambda (x) (> x (first l))) (rest l)))
      )
  )
)

Aufruf z.B. mit

(my-quicksort '(8 7 5 -4 2 22 9 4 -4 4 11 1 0))

produziert

(list -4 -4 0 1 2 4 4 5 7 8 9 11 22)

Wichtige Scheme Funktionen

Samstag, 28. November 2009

foldr und foldl

Die Funktion fold wendet eine Funktion auf jedes Element einer Liste an, speichert das Ergebnis in einer neuen Liste und gibt diese zurück.
foldr liest die Liste von links nach rechts ein. foldl von rechts nach links, also falsch herum. Daraus ergibt sich auch gleich eine sehr hilfreiche Anwendung von foldl:

> (foldl cons empty (list 1 2 3 4 5))
(list 5 4 3 2 1)

Es invertiert also einfach die übergebene Liste.

Ein anderes Beispiel (bei dem es keinen Unterschied macht, ob man foldr oder foldl nutzt):

> (foldr + 0 (list 1 2 3 4 5))
15

map

map wendet eine Funktion auf alle Elemente einer Liste an, und gibt die jeweiligen Ergebnisse als neue Liste zurück.

> (map (lambda (x) (* x x)) (list 1 2 3 4 5))
(list 1 4 9 16 25)

filter

filter filtert eine Liste. Dazu übergibt man eine Funktion die entweder true zurückgibt, wenn das gerade getestete Element in die neue Liste soll, oder eben false, wenn es nicht in die neue Liste soll.

> (filter even? (list 1 2 3 4 5))
(list 2 4)

Scheme Kurs – Teil 7: IO

Samstag, 28. November 2009

Bis jetzt habe ich noch nichts zur Ein- und Ausgabe von Daten in Scheme gesagt. Simple Ausgaben kann man realisieren indem man einfach die Variable hinschreibt, ohne sie auf eine Funktion anzuwenden.

(define hallo-welt "Hallo Welt!")
hallo-welt

Es gibt aber auch eine Menge Funktionen mit denen man Ausgaben bewältigen kann. Darunter ist das aus C/C++ bekannte printf.
Ich möchte nicht näher auf die Ausgabefunktionen eingehen, da sie keine große Schwierigkeit darstellen. Alle nötigen Informationen dazu findet man unter:
http://download.plt-scheme.org/doc/html/reference/Writing.html

Das gleiche gilt für die Eingabe von Daten. Alle nötigen Informationen findet man hier:
http://download.plt-scheme.org/doc/html/reference/Reading.html

Ein kleines Beispiel zum Abschluss:

(printf "Bitte geben Sie Ihren Name ein: ")
(define name (read))
(printf "Hallo ~v" name)

Scheme Kurs – Teil 6: set!

Samstag, 28. November 2009

Bis jetzt haben wir in Scheme nur mit Konstanten gearbeitet, die man einmal mit einem Wert belegen kann.
Mit set! kann man aber den Wert einer Variable ändern. Davor muss man die Variable aber wie eine Konstante mit define erstellen, dass heißt, wir haben bis jetzt auch schon mit Variablen gearbeitet, nur konnten wir deren Wert noch nicht ändern.

(define i 0)
(set! i 1)
(set! i (+ i 1))

Die Variable i hat nach der ersten Zeile den Wert 0, da sie mit define mit dem Wert 0 definiert wird.
Nach Zeile 2 ist der Wert von i 1, da wir es explizit mit set! festlegen.
In Zeile 3 nutzen brechnen wir den neuen Wert von i mit dem alten (oder bisherigem) Wert von i. i ist also nach dieser Zeile 2.

Das Ausrufezeichen ist eine Scheme Konvention. Man muss sich nicht daran halten, aber es ist extrem sinnvoll. Denn so kann jeder sofort erkennen dass eine Funktion eine oder mehrere Zuweisungen vornimmt.
Wenn man also eine Funktion schreibt, die einen Parameter verändert, dann sollte man an den Funktionsname ein Ausrufezeichen anhängen.

Scheme Kurs – Teil 5: local

Freitag, 30. Oktober 2009

Mit dem Schlüsselwort local kann man in Scheme einen neuen Namesraum definieren, in dem man Variablen und Funktionen völlig normal erstellen und nutzen kann, mit dem Unterschied das diese Variablen und Funktionen nach Ende des local Bereiches nicht mehr existieren – der Name ist also wieder frei und kann erneut vergeben werden.

Beispiel:

> (define x 5)
> x
5
> (local ((define x 10)) x)
10
> x
5

Syntax:

(local (<expression1> <expression2> <...>) (<expression3>))

In der Klammer in der expression1 und expression2 stehen können beliebig viele variable Eigenschaften definiert werden. Danach (im den Klammern in denen expression3 steht) wird dann die eigentliche Logik geschrieben.

local ist wichtig wenn man eine temporäre Variable braucht, in einer rekursiv aufgerufenen Funktion.

Scheme Kurs – Teil 4: Listen und Prädikate

Freitag, 23. Oktober 2009

Oft weiß man nicht von Anfang an wie viele Elemente man abarbeiten muss. Deshalb kann man diese auch nicht in einem statischem Datentyp speichern, sondern man muss auf dynamische Datentypen zurückgreifen. Bei diesen kann man die Anzahl der Elemente auch zu Laufzeit problemlos ändern. Solche Datentypen sind zum Beispiel Listen.
Diese Strukturen werden auch als rekursive Datenstrukturen bezeichnet, weil jedes Element in der Liste eine Instanz zu seinem Nachbarn hat. Jedes Element kennt als das Element “rechts” neben ihm (wo auch immer rechts sein mag). Dabei ist darauf zu achten, das es einen speziellen Wert gibt, den nur das letzte Element besitzt um zu signalisieren das es kein Element mehr nach ihm gibt.

Rekursion umgangssprachlich beschrieben:

Irgendwo in der eigenen Definition (z.B. Listen(-feld)- oder Funktionsdefinition) ist ein Verweis auf mich selbst, oder auf etwas gleichen Typs.

Eine Liste erstellen

Eine Liste wird ähnlich wie eine Variable definiert:

(define liste
   (cons 'element1 (cons 'element2 empty)))

Wir haben also eine Liste mit dem Name liste erstellt mit 2 Elementen. Das dritte Element empty ist das oben erwähnte Element welches signalisiert das das Ende der Liste erreicht wurde. Die Funktion cons (engl. construct) erwartet immer 2 Parameter: Der Erste ist das Element was man an die Liste anfügen will, und im 2. Parameter kann man entweder das nächste Listenelement anhängen oder man setzt das empty Element.

Man kann jeden beliebigen Datentyp in der Liste speichern, auch gemixt – und auch neue eigenständige Listen. Zum Beispiel kann das erste Element ein Symbol, das Zweite eine Zahl, das Dritte ein String usw. sein. Es ist bloß wichtig darauf zu achten, dass man prüft welchen Typs das aktuelle Element ist, bevor man es verwendet.

Es gibt noch eine weitere Möglichkeit eine Liste zu erstellen, und zwar mit der Funktion list:

(list 52 'hallo liste 'und 'so 'weiter)

Der Funktion list kann man beliebig viele Parameter übergeben, auch der Typ ist egal!

Nächster Bitte!

Stellen wir uns die Liste die wir erstellt haben nun bildlich vor:

| 'element1 |---->| 'element2 |----> empty

Mit Scheme kann man nur auf das erste Element (first) der Liste zugreifen.

(first liste) -> 'element1

Hier sehen wir wie man auf das erste Element in der vorhin definierten Liste zugreift. Dabei gibt die Funktion das entsprechende Symbol ‘element1 zurück.
Will man auf das zweite, oder irgendein anderes Element zugreifen so überspringt man das Element davor. Dazu gibt es die Funktion rest. Wenn man die Prozedur rest auf eine Liste anwendet, wird das erste Element der Liste fallen gelassen und alle übrig geblieben Elemente sind die neue Liste.

(define neue-liste (rest liste))

Die neue Liste sieht also so aus:

| 'element2 |----> empty

Jetzt kann man mit first auf das vormals Zweite, jetzt aber erste Element zugreifen.

empty?

Man kann die Prozedur rest ein weiteres mal auf die Liste neue-liste anwenden, dann zeigt das erste Listenelement aber auf empty – die Liste ist also leer. Wenn man jetzt versucht mit first auf das Element zuzugreifen gibt es einen Fehler!
Damit das nicht passiert gibt es so genannte Prädikate.
Mit der Funktion empty? kann man abfragen ob das aktuelle Element das spezielle empty Element ist.

(empty? (rest neue-liste)) -> true

andere Prädikate

Es gibt noch eine Menge anderer Prädikate. Zum Beispiel hat jeder Datentyp sein eigenes.

(symbol? 'test) -> true
(symbol? 42) -> false

(list? liste) -> true

Beliebiges Potenzieren mit Scheme

Donnerstag, 22. Oktober 2009

Leider gibt es in Scheme keine Funktion um eine Zahl beliebig zu potenzieren. Daher hab ich eine kleine Funktion dafür geschrieben:

;; Contract: ^ number number -> number
;; Purpose: Returns result of number1^number2
;; Example: (^ 2 10) -> 1024
;;          (^ 2 -2) -> 0.25
(define (^ x exp)
  (cond
    [(> exp 1) (* x (^ x (- exp 1)))]
    [(< exp 0) (/ 1 (* x (^ x (- (- exp) 1))))]
    [(= exp 1) x]
    [(= exp 0) 1]))

(check-expect (^ 2 10) 1024)
(check-expect (^ -52 0) 1)
(check-expect (^ 2 -3) (/ 1 8))

Scheme Kurs – Teil 3: Strukturen (struct)

Dienstag, 20. Oktober 2009

Strukturen gibt es in vielen Programmiersprachen. Sie sind auch sehr sinnvoll, denn oft gilt es, Daten zu irgendetwas zusammen zu fassen. Dabei kann es sich z.B. um eine Person handeln. Jeder Mensch hat einen Vorname, Nachname, Wohnort, etc. Da bietet sich das erstellen einer Struktur an, in der man alle Daten die zu einem Menschen gehören zusammen fasst.

In Scheme definiert man eine Struktur (engl. struct) mit:

(define-struct human (forname surename residence))

Zuerst gibt man an, dass man eine Struktur definieren möchte. Danach folgt der Name der neuen Struktur und in einer neuen Klammer alle Eigenschaften die die Struktur haben soll (Eigenschaften des Konstruktors).
Danach hat man die Struktur definiert, jetzt muss man noch eine Struktur erstellen, also einen Teil des Speichers reservieren und die bei der Strukturdefinition angegebenen Eigenschaften mit Werten füllen. Man kann auch sagen, man erstellt eine Instanz der Struktur:

(define peter-mustermann
    (make-human
        ('Peter 'Mustermann 'Berlin)))

Die Instanz erstellt man mit make-human, wobei human natürlich der Name der Struktur ist, gefolgt von einer neuen Klammer mit den Werten die die Struktur speichern soll. Man übergibt die Werte an einen sogenannten Konstruktor.

Wer bist du?

Wenn man nicht weiß, welche Struktur in einer Variable gespeichert ist, kann man dafür die sogenannte Prädikat-Prozedur nutzen.

(human? peter-mustermann) -> boolean

Die Prädikat-Prozedur ist der Name der Struktur, gefolgt von einem Fragezeichen. Als einzigen Parameter übergibt man die fragwürdige Variable. Die Prozedur gibt einen boolean-Wert zurück. In diesem Fall natürlich true.

Wie heißt du, und wo kommst du her?

Möchte man auf eine bestimmte Eigenschaft der Struktur zugreifen und deren Wert auslesen so benutzt man einen sogenannten Selektor:

(human-forename peter-mustermann)

Der Selektor ist der Name der Struktur gefolgt von dem Name der Eigenschaft die man auslesen will. Dazu muss man noch die Variable mit der entsprechenden Struktur angeben. Die Funktion gibt daraufhin den Wert zurück.

Abstraktion

Strukturen können sehr gut zur Datenabstraktion genutzt werden, denn sie vereinen zusammen gehörige Daten und helfen diese strukturierter und modularer zu verarbeiten. Des weiteren lässt sich eine Source, die mit structs arbeitet wesentlich besser lesen, somit erhöhen sie auch die Wartbarkeit von Programmen.
Die Schnittstellen zu den Strukturen stellen die Konstruktoren und Selektoren dar.

Scheme Kurs – Teil 2: Symbole, Bedingungen, Boolsche Funktionen und Tests

Samstag, 17. Oktober 2009

Symbole

Ein Symbol ist eine Zeichenfolge ähnlich wie ein String, mit dem Unterschied das Symbole nicht manipuliert werden können. Dadurch sind sie sehr viel schneller als Strings. Symbole werden durch ein einfach Anführungszeichen begonnen und enden am nächsten Space, es gibt also kein Ausführungszeichen.

> (define hallo 'Hallo)
> hallo
'Hallo
> 'Welt!
'Welt!

Bedingungen

Bedingungen werden in Scheme mit folgendem if-Ausdruck realisiert:

(if <test>
    <then>
    <else>)

Wichtig ist, dass der else-Zweig nicht optional ist!

Dieses Beispiel testet ob eine Variable negativ oder positiv ist und gibt das Ergebnis aus:

> (define variable 5)
> (if (> 0 variable)
      'positiv
      'negativ)
'positiv

Es gibt noch eine andere Form einer Bedingung mit beliebig vielen Tests. Hierbei ist der else-Zweig allerdings optional.

(cond [<test 1> <then 1>]
      [<test 2> <then 2>]
      [<test 3> <then 3>]
      ...
      [else <else>])

Boolesche Funktionen

Es gibt in Scheme natürlich auch die gewohnten Booleschen Funktionen and und or. Bei and muss alles true sein, bei or nur min. ein Test. Des weiteren gibt es auch ein not und boolean=? Statement. Ein boolean=? testet ob die beiden übergebenen Werte gleich sind.

(and <test 1> <test 2>)
(or <test 1> <test 2>)
(not <test>)
(boolean=? <expression 1> <expression 2>)

Zu Beachten ist, das es eine Shortcut Regel gibt. Es werden also bei z.B. einem and nicht alle Tests ausgewertet, wenn bereits einer false war.

Tests

Um zu überprüfen ob eine Funktion den richtigen Wert zurück liefert oder eine Variable den richtigen Wert hat, stellt Scheme zwei Funktionen zur Verfügung:

(check-expect <actual> <expected>)
(check-within <actual> <expected> <delta>)

Es gibt noch eine weitere Funktion die die Fehlermeldung einer eigenen Funktion prüft:

(check-error <test> <expected message>)