[Informatik-Seminar WS2004/05] [Inhalt] [Zurück] [Weiter]
Dieses Kapitel befasst sich nun genauer mit der eigentlichen Sprache. Es werden die Sprachelemente, die Syntax, die Funktionsweise und einige typische Eigenschaften erklärt.
Der Typ Liste wird im folgenden Kapitel erläutert, weil dieser in Scheme (bzw. LISP) eine herausragende Rolle spielt.
3.3 Konvertieren zwischen Datentypen
Kommentare werden durch ein Semikolon eingeleitet. Alle folgenden Zeichen in dieser Zeile werden ignoriert.
; Dies ist ein Kommentar
In Scheme gibt es einfache und kombinierte Datentypen. Ausdrücke, die auf einfachen Datentypen basieren, werten sich selber aus. Kombinierte Datentypen entstehen durch das Kombinieren von einfachen Datentypen.
Scheme verwendet für die Darstellung von false und true folgende Ausdrücke:
#f ; false
#t ; true
Eine Negation ist mit der Funktion not
möglich:
(not #f) => #t
(not #t) => #f
Soll ein Ausdruck als Wahrheitswert ausgewertet werden, wertet Scheme alle
Ausdrücke, außer #f
, als #t
aus.
(not "Hallo") => #f
Wahrheitswerte können mit and
und or
verknüpft
werden. Die Funktionen lassen mehrere Argumente zu. Diese werden von links nach
rechts ausgewertet. Sobald das Ergebnis feststeht, weil and
einen Ausdruck als #f
oder or einen Ausdruck als #t ausgewertet hat, brechen die Funktionen ab und
liefern das Ergebnis.
(and #t #t) => #t
(and #t #f) => #f
(or #f #f #f #f #f #t) => #t
(and #f UndefinerterBezeichner)
=> #f, weil der unbekannte Bezeichner nicht ausgewertet wird
(and #t UndefinerterBezeichner)
=> Fehler, weil ein unbekannter Bezeichner nicht ausgewertet werden kann.
Ob ein Ausdruck ein Wahrheitswert ist, lässt sich mit der Funktion
boolean?
testen.
(boolean? #f) => true
(boolean? "Hallo") => false
Es gibt, wie in der Mathematik natürliche (z.B. 23), rationale (z.B. 23/42), reelle (z.B. 23.23) und komplexe (23+42i) Zahlen. Auch hierfür gibt es Funktionen, die testen, ob ein Ausdruck dem Typ entspricht.
(integer? 42) => #t
(integer? 23/42) => #f
(rational? 1/4) => #t
(rational? 0.25) => #t
(rational? 2+3i) => #f
(real? 1.23) => #t
(real? 2+3i) => #f
(complex? 2+3i) => t
Für natürliche Zahlen kann auch die binäre, oktale oder hexadezimale Schreibweise verwendet werden. Dies geschieht mit Hilfe von Präfixen. Die Angabe des dezimalen Präfixes ist optional:
#b11 ;binär => 3
#o11 ;oktal => 9
#x11 ;hexadezimal => 17
#d11 ;dezimal => 11
Um Zahlen zu vergleichen sind folgenden fünf Funktionen verfügbar:
(= 23 42) => #f
(= 23 23) => #t
(< 23 42) => #t
(<= 23 42) => #t
(> 23 42) => #f
(>= 23 42) => #f
In Scheme sind eine Reihe von mathematischen Funktionen definiert. Hier ein paar Beispiele:
(+ 1 2 3) => 6
(- 42 2) => 40
(- 20 10 5) => 5
(* 2 3) => 6
(/ 10 2) => 5
(expt 2 10) => 1024
(max 1 2 3) => 3
(min 1 2 3) => 1
(abs -42) => 42
Ein Zeichen wird durch das Präfix #\
dargestellt. #\a
ist also der Buchstabe a. Außerdem gibt es nicht grafische Zeichen, wie z.B.
#\newline
, #\tab
oder #\space
.
Hier gibt es ebenfalls eine Testfunktion:
(char? #\a) => #t
(char? b) => #f
Einfache Datentype werten sich selber aus. Anders ist dies bei Symbolen, die ähnlich wie eine Variable funktionieren. Diese stehen für einen Wert, der dem Symbol zugewiesen wurde. Ein Symbol wird durch seinen Namen beschrieben. Dieser darf aus Buchstaben, Zahlen und allen anderen Zeichen bestehen. Ein einfaches Anführungszeichen (') ist nicht erlaubt. Symbole werde nicht nach Groß- und Kleinschreibung unterschieden.
Mit dem einfachen Anführungszeichen wird eine Auswertung verhindert. Diese
Schreibwiese ist die Kurzform der Funktion quote
. Diese gibt das
Symbol selbst aus.
'abc => abc
(quote abc) => abc
Mit folgender Funktion lässt sich testen, ob das Argument ein Symbol ist:
(symbol? 'abc) => #t
(symbol? 42) => #f
Mit der allgemein verwendbaren Funktion eqv?
lassen sich auch
Symbole auf Gleichheit testen. Die Funktion ist auch für alle andere Datentypen
verwendbar.
(eqv 'abc 'ABC) => #t
(eqv 'abc 'xyz) => #f
Symbole können als globale Variable verwendet werden. Zu beachten ist, dass
in der funktionalen Programmierung eine erneute Zuweisung eines Wertes verboten
ist. Dazu wird dem Symbol mit der Funktion define
ein Wert
zugewiesen.
(define x 23)
Hier hat das Symbol x den Wert 23 erhalten. Soll nun dieses ausgewertet werden, liefert es den Wert 23.
x => 23
Kombinierte Datentypen entstehen durch die Kombination von Datentypen. Dazu gehören Strings, Vektoren und Listen.
Strings entstehen durch die Kombination von Zeichen. Dies kann auf zwei Arten
geschehen. Entweder durch die Angabe eines Strings in doppelten
Anführungszeichen oder durch das Verwenden der string
Funktion, die
einen String konstruiert.
"Hallo Welt" => "Hallo Welt"
(string #\H #\a #\l #\l #\o #\space #\W #\e #\l #\t) => "Hallo Welt"
Mit der Funktion string?
lässt sich ein Ausdruck auf den Typ
String testen.
(string? "Hallo") => #t
Mehrere Strings können mit der Funktion string-append
zusammengefügt werden.
(string-append "Hallo" (string #\space) "Welt") => "Hallo Welt"
Vektoren sind eindimensionale Listen. Diese können alle Arten von Datentypen aufnehmen. Dies schließt auch Vektoren selbst ein.
Vektoren werden mit der Funktion vector
erzeugt. Scheme stellt
einen Vektor durch die Angabe der Anzahl der enthaltenen Elemente und durch die
Elemente, welche von Klammern umgeben sind, dar.
(vector 1 2 3) => #3(1 2 3)
Auch hier gibt es wieder eine Funktion, um auf diesen Typ zu testen:
vector?
(vector? #1(1)) => #t
(define v1 (vector 1 2 3))
(vector? v1) => #t
Scheme liefert eine Vielzahl von Funktion, um Daten von einem Typ in einen
anderen umzuwandeln. Die Namen der Funktionen setzten sich aus dem Quelltyp, den
Zeichen ->
und dem Zieltyp zusammen. Ist eine Konvertierung nicht
möglich, liefert die Funktion #f
.
Folgende Konvertierungen sind möglich:
Bei der Umwandlung von char nach interger wird der ASCII Code verwendet.
(char->integer #\a)
=> 97
(string->list "hallo") => (#\h #\a
#\l #\l #\o)
(number->string 42) => "42"
(symbol->string 'Symbol) => "Symbol"
Die Funktionen number->string
und
string->number
können ein optionales
zweites Argument verarbeiten: Die Basis des Zahlensystems. Die Funktionen
wandeln dann die Darstellung entsprechend um.
(number->string 11 10) => "11"
(number->string 11 2) => "1011"
(number->string 11 16) => "b"
(string->number "1011" 2) => 11
(string->number "b" 16) => 11
Eine Prozedur oder Funktion wird mittels define
und lambda
definiert. Hinter
define
folgt, wie üblich, der Name. Anschließend werden
durch lambda
die Funktionsargumente und der Funktionsrumpf definiert.
(define Funktionsname
(lambda (Funktionsargument1 Arg2) (Funktionsrumpf) ))
Dieses Beispiel addiert zu einer Zahl 23:
(define add23
(lambda (x) (+ x 23)))
(add23 5) => 28
Dies lässt sich auch mittels define
verkürzt so schreiben:
(define (add23 x) (+ x 23))
Als lokale Symbole sind bereits die lamba-Parameter bekannt. Diese haben als
Namensraum den Funktionsrumpf. Zusätzlich können lokale Symbole mittels let
deklariert werden. Dazu werden erst die lokalen Symbole angegeben. Anschließend
folgt der Code, in dem diese Symbole dann definiert sind.
Hierzu ein kleines Beispiel:
(define (daneben x)
(let ( (richtig 42) )
(- richtig x) ))
(daneben 40) => 2
In Scheme gibt es mehrere Möglichkeiten Bedingungen zu formulieren. Schleifen sind aber nicht möglich, diese würde dem Prinzip der funktionalen Programmierung widersprechen.
Die einfachste Form ist das if
. Die Funktion if wertet zuerst
das erste Argument aus. Ist dies nicht #f wird das zweite Argument ausgewertet
und als Ergebnis zurückgegeben. Das dritte Argument ist der optionale else
Teil.
(define x 42)
(if (= x 42) "richtig" "falsch" )
=> "richtig"
(define x 42)
(if (= x 42) "richtig")
=> "richtig"
Anstatt mehrere if-else Bedingungen zu schachteln, kann die Funktion
cond
verwendet werden. Die Argumente müssen jeweils eine Bedingung und
einen Ausdruck, der ausgewertet werden soll, wenn die Bedingung #t
ist, enthalten. Als letztes Argument ist ein else
Teil möglich, der
ausgewertet wird, wenn keine Bedingung #t
lieferte.
(define x 44)
(cond ((< x 42) "kleiner")
((> x 42) "größer")
(else "richtig"))
=> "größer"
Soll der gleiche Ausdruck mit mehreren Werten auf Gleichheit geprüft werden,
bietet sich die case
Funktion an. Diese vergleicht das erste
Argument mit den folgenden Ausdrücken. Sobald ein Ausdruck gleich dem ersten
Argument ist, wird der zugehörige Ausdruck ausgewertet und als Ergebnis
zurückgegeben.
(define x 41)
(case x
((42) "richtig")
((41 43) "knapp falsch")
(else "falsch")
)
=> "knapp falsch"
Damit dies, wie hier beschrieben, funktioniert, müssen diese Funktionen von Scheme besonders behandelt werden. Normalerweise werden Argumente erst ausgewertet und dann an die aufgerufene Funktion übergeben. Hier werden die Argumente natürlich nur bei Bedarf ausgewertet.
In funktionalen Programmiersprachen ist es besonders wichtig, dass Funktionen auch Parameter und Ergebnisse sein können.
Funktionen können in Scheme, wie jeder andere Datentyp, Parameter sein. Dazu wird einfach der Funktionsname übergeben. Hierzu dieses kurze Beispiel:
(define (DoIt fkt arg1 arg2)
(fkt arg1 arg2))
(DoIt + 1 2) => 3
Es ist ebenfalls problemlos möglich, Funktionen als Ergebnis zurückzugeben. Dazu wird einfach der Name der Funktion angegeben.
Dazu wieder ein kleines Beispiel:
(define (sub1 x)
(- x 1))
(define (add1 x)
(+ x 1))
; Hier wird zurückgegeben, welche Funktion ausgeführt werden muss,
; um der 42 näher zu kommen.
(define (WhatToDo x)
(if (< x 42)
add1
sub1))
; Dies liefert den Funktionsnamen zurück
(WhatToDo 10) => #<procedure:add1>
; Hier wird die Funktion auch ausgeführt
((WhatToDo 10) 10) => 11
[Informatik-Seminar WS2004/05] [Inhalt] [Zurück] [Weiter] [Seitenanfang]