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:

Prinzip zur Bildung von Tupeln. Wenn 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:

wird zu

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.

Übungsaufgabe Schreiben Sie eine Funktion 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
Übungsaufgabe Schreiben Sie eine Funktion insideTriangle p q r v, die überprüft, ob \(v\) innerhalb des von \(p,q,r\) aufgespannten Dreiecks liegt.