2. Elm
2.7 Tupel
Sie haben bis jetzt vier Datentypen kennengelernt: Int
, Float
,
String
und Bool
. Dass statt Int
und Float
manchmal
der "Obertyp" number
verwendet wird, sollten Sie weitestgehend ignorieren.
Im letzten Abschnitt haben Sie aber gesehen, dass es zu diesen primitiven Datentypen auch
zusammengesetzte geben kann, nämlich Paare.
("hello", String.length "hello")
("hello", 5) : (String, Int)
Hier ist also ein neuer Datentyp: (String, Int)
. Mithilfe von Paaren
können wir also aus den vier uns bislang bekannten Typen Int, Float, Bool, String
16 verschiedene Kombinationen herstellen. Generell gilt:
x
ein Ausdruck vom Typ A
ist und y
ein Ausdruck vom Typ
B
,
dann ist
(x,y)
ein Ausdruck vom Typ (A, B)
.
Aber..., Paaare, also Tupel, sind doch selbst wieder Typen. Kann ich das obige Prinzip also
mit x = ("hello", String.length "hello")
und y = "English"
anwenden
und einen
Ausdruck der Form
(("hello", String.length "hello"), "English")
bilden, und hat dieser Ausdruck dann
den Typ
((String, Int), String)
? Probieren wir es aus!
(("hello", String.length "hello"), "English")
(("hello",5),"English") : ( ( String, Int ), String )
Wunderbar! Wir können also Paare verschachteln und erhalten so eine im Prinzip unendliche Menge möglicher Typen! Und da war doch noch etwas: Funktionen sind doch selbst Ausdrücke, und ihr Typ ist ihre Signatur, oder?
f : Int -> Int
f x = n^2
<function> : Int -> Int
(f, "Quadratfunktion")
(<function>,"Quadratfunktion") : ( Int -> Int, String )
In Elm sind Funktionen vollberechtigte Staatsbürgerinnen; sie haben somit auch einen Typ und dürfen Teil eines Tupels sein.
Im Kapitel über die Fibonacci-Zahlen haben wir bereits gesehen, wie nützlich Tupel sein können.
Noch offensichtlicher wird es, wenn wir geometrische Probleme programmieren. Denn
Punkte und Vektoren im zweidimensionalen Raum sind ja "von Haus aus" Objekte
vom Typ (Float, Float)
.
Tupel verarbeiten
Ein neues Tupel zu erschaffen geht ganz einfach:
pair = (4.0, 7.0)
(4, 7): (Float, Float)
Auf die erste und zweite Komponente können Sie mittels Tuple.first
und
Tuple.second
zugreifen:
pair = (4.0, 7.0)
(4, 7): (Float, Float)
Tuple.first pair
4 : Float
Tuple.second pair
7 : Float
Übungsaufgabe Ich definiere nun ein verschachteltest Tupel:
nestedPair = ("hello", ( (3.0, "my"), "dear"))
Dies ist ein Paar, dessen erste Komponente ein String und dessen zweite Komponente wiederum ein Paar ist etc.
Zeigen Sie, wie Sie mittels Tuple.first
und Tuple.second
auf
die Einzelbestandteile wie 3.0
oder "dear"
zugreifen können!
Geometrische Probleme
Als erstes will ich mit Ihnen eine Funktion schreiben, die einen Vektor \(\begin{pmatrix}x \\ y \end{pmatrix}\) um einen gegebenen Winkel \(\alpha\) nach links rotiert:
Bezeichnen wir diese Funktion, also Rotation nach links um einen Winkel von \(\alpha\), mit dem Symbol \(R_{\alpha}\). Wie finden wir eine Formel für \(R_{\alpha}\begin{pmatrix}x \\ y \end{pmatrix}\)? Eine entscheidende Beobachtung ist, dass wir den Vektor in seine \(x\)- und seine \(y\)-Koordinate zerlegen können und diese Vektoren getrennt rotieren:
Die zweite Beobachtung ist, dass wir, um \(\begin{pmatrix}x \\ 0 \end{pmatrix}\) zu rotieren, einfach \(\begin{pmatrix}1 \\ 0 \end{pmatrix}\) rotieren und das Ergebnis dann wiederum um \(x\) skalieren können. Das gleiche gilt für \(\begin{pmatrix}0 \\ y \end{pmatrix}\). Mathematisch ausgedrückt: die Funktion \(R_\alpha\) ist linear, und daher gilt
\begin{align*} R_{\alpha} \begin{pmatrix}x \\ y \end{pmatrix} = x \cdot R_\alpha \begin{pmatrix}1 \\ 0 \end{pmatrix} + y \cdot R_\alpha \begin{pmatrix}0 \\ 1 \end{pmatrix} \ . \end{align*}Wir müssen also nur herausfinden, wie \(R_\alpha\) auf den Standardeinheitsvektoren operiert.
Übungsaufgabe Schreiben Sie eine Funktion
rotate : Float -> (Float, Float) -> (Float, Float)
rotate angle vector =
...
die einen gegebenen Punkt um den gegebenen Winkel rotiert. Achten Sie darauf, dass die
Funktionen
cos
und sin
Bogenmaß und nicht Grad verwendet; statt 90 Grad
müssen Sie also
\(\pi/2\) schreiben.
Wenn wir zwei Punkte \(p,q\) gegeben haben, dann können wir die Gerade \(\overline{pq}\) betrachten. Wenn diese auch noch eine Orientierung hat (von \(p\) nach \(q\)), dann teilt sie die restliche Ebene ein in die Punkte, die links von ihr liegen und die, die rechts von ihr liegen:
Wie finden wir nun heraus, ob ein weiterer Punkt \(r\) auf der linken oder rechten Seite liegt? Zu Hilfe kommt hier das innere Produkt: wenn Sie zwei Vektoren \(u = \begin{pmatrix}u_1 \\ u_2 \end{pmatrix}\) und \(v = \begin{pmatrix}v_1 \\ v_2 \end{pmatrix}\) haben, dann ist
\begin{align*} \scalar{u}{v} := u_1 v_1 + u_2 v_2 \end{align*}ihr inneres Produkt. Ist es positiv, so ist der Winkel zwischen \(u\) und \(v\) kleiner als 90 Grad (spitz); ist es negativ, dann ist er größer als 90 Grad (stumpf); ist es 0, dann liegen die beiden Vektoren in einem 90-Grad-Winkel. Sie kennen das innere Produkt eventuell unter dem Namen Skalarprodukt. Betrachten wir jetzt die Gerade und einen weiteren Punkt \(v\):
Wir verschieben das Bild, so dass \(p\) auf dem Ursprung liegt:
Dann rotieren wir den Vektor \(q-p\) um 90 Grad (also um \(\pi/2\)) nach links:
Nun können wir mithilfe des inneren Produktes \begin{align*} \scalar{R_{\pi/2} (q-p)}{v-p} \end{align*} überprüfen, ob der Winkel \(\theta\) im obigen Bild spitz oder stumpf ist. Dann wissen wir auch, ob \(v\) links oder rechts von der gerichteten Geraden \(\vec{pq}\) liegt.
leftRightOrOn
, die als Argument eine gerichtete Gerade \(\vec{pq}\) und einen Punkt
\(v\)
entgegennimmt und dann 1, -1 oder 0 ausgibt, je nachdem, ob \(v\) links von, rechts von oder auf
der gerichteten
Gerade \(\vec{pq}\) liegt.
- Legen Sie zuerst die Signatur der Funktion fest. Mit welchem Datentyp repräsentieren Sie die Gerade?
- Schreiben Sie eine Funktion
innerProduct
, die \(\scalar{u}{v}\) berechnet. - Schreiben Sie eine Funktion
vectorFromTo p q
, die \(q - p\) berechnet. - Schreiben Sie jetzt
leftRightOrOn
insideTriangle p q r v
, die überprüft, ob
\(v\) innerhalb des von \(p,q,r\) aufgespannten Dreiecks liegt.