3. Zusammengesetzte Datentypen
3.2 Erstes Programmierprojekt: Geometrie, Grafik und Modularisierung
Abgabetermin: Sonntag, 3. Dezember 2023.
Aufgaben: Die in diesem Teilkapitel (3.2) als Testataufgabe gekennzeichneten Aufgaben.
Format: Elm-Quelldateien, wie weiter unten beschrieben.
Material: Die Quelldateien des Projekts mit einer fehlerhaften Dummy-Implementierung von mir: project-1-geometry.zip. Wie Sie mit diesen Dateien vorgehen, ist weiter unten genauer beschrieben.
In diesem Projekt werden wir zusammen mehrere Applikationen programmieren. Ein Beispiel sehen Sie hier:
In dieser App können Sie bis zu drei Punkte in das Spielfeld klicken. Sobald Sie drei Punkte haben, wird das Dreieck eingefäbt, und zwar abhängig davon, ob die Maus innerhalb oder außerhalb des Dreiecks ist.
Modularisierung
Eins der wichtigsten Prinzipien beim Programmieren ist die Modularisierung. Das heißt, dass Sie Ihren Code in sinnvolle, wiederverwertbare Einheiten (genannt Module) trennen. Um ein Modul verwenden zu können, muss ich wissen, was es implementiert, nicht wie es implementiert wird. In etwa so, wie Sie beim Kauf einer Glühbirne die Spezifikationen (Spannung, Leistung, Art des Gewindes) wissen müssen, aber eben nichts davon verstehen müssen, wie die Glühbirne funktioniert.
Modularisierung dient der Zusammenarbeit. Sie werden den Großteil Ihres Arbeitslebens nicht alleine programmieren, sondern in Zusammenarbeit mit anderen. Dies tun Sie auch im Rahmen dieses Projekts.
Was ich mache: ich schreibe in diesem Projekt
das "Front End", also die grafische Benutzeroberfläche.
Im Moment würde das auch Ihre Kenntnisse übersteigen.
Mein Code benutzt ein Modul
GeometryTypes, welches die Typen
Vector, DirectedLine
und Triangledefiniert.
Diese Modul stelle ich Ihnen bereit.
Des Weiteren benutzt es ein Modul GeometryFunctions,
welches die folgenden Funktionen implementiert:
leftRightOrOnLine : DirectedLine -> Vector -> IntinsideTriangle : Triangle -> Vector -> BoolpointOnLineClosestTo : DirectedLine -> Vector -> Vector
Was Sie machen: Sie implementieren das
Modul GeometryFunctions, also
die verlangten drei Funktionen.
Worüber wir kommunizieren müssen:
Stellen Sie sich vor, Sie wären ein Team von Softwareentwicklern und
ich wäre ein anderes Team. Unsere Kompetenzen sind verschieden:
Ihr Team besteht aus Spezialisten für geometrische Algorithmen;
mean Team besteht aus Spezialisten für Webentwicklung,
Frontends und grafische Benutzeroberflächen. Es gibt
nur wenig Überschneidung unserer Kompetenzen. Daher ist es
wichtig, die Schnittstelle kleinzuhalten, also das,
worüber wir kommunizieren müssen, was wir beide verstehen müssen.
Und die Schnittstelle ist eben genau dies:
die Typen, die in GeometryTypes definiert sind,
und die Signaturen der Funktionen, die Sie in
GeometryFunctions implementieren sollen. Am wichtigsten
und schwierigsten aber: was diese Funktionen denn tun sollen,
also deren Semantik. Versuchen wir es.
leftRightOrOnLine line vgibt 1, -1, oder 0 zurück, je nachdem, ob der Punktvlinks von, rechts von oder auf der gerichteten Geradelineliegt.insideTriangle triangle vgibtTruezurück, wennvim Inneren des Dreiecks liegt, ansonstenFalse.-
pointOnLineClosestTo line vgibt den Punktuauf der Geradelinezurück, der am nächsten zuvliegt; also den Fußpunkt, wenn Sie vonvaus ein Lot auflinefällen.
Meine Dummy-Implementierung und wie Sie sie starten.
"Mein" Code, also die grafische Oberfläche, ist schon fertig.
Damit Sie gleich starten können, habe ich eine Dummy-Implementierung
von GeometryFunctions geschrieben. Darin sind die
Funktionen zwar alle vorhanden, geben aber inkorrekte Werte
zurück. Der Zweck ist, dass Sie einen lauffähigen Code haben,
auf dem Sie aufbauen können.
Laden Sie sich nun project-1-geometry.zip herunter,
speichern es in Ihrem PP-Order, also unter H:\PP\ und entkomprimieren
ihn dann. Gehen Sie auf der Konsole in den dekomprimierten Ordner.
C:\> H:\H:\> cd PP\project-1-geometryH:\PP\project-1-geometry\>
Sie können jetzt natürlich Elm im Relp-Modus starten und mit meiner Dummy-Implementierung rumspielen:
H:\PP\project-1-geometry\> elm replimport GeometryTypes exposing (..)import GeometryFunctions exposing (..)p = {x = 0, y = 0}q = {x = 3, y = 1}line = {from = p, to = q}v = {x = 1, y = 0}leftRightOrOnLine line v1 : Int
Der Code läuft also, ist aber nicht korrekt. Der Punkt
\(v\) liegt nämlich rechts von der gerichteten Geraden, die
Funktion hätte also -1 ausgeben müssen (zeichnen
Sie die drei Punkte in einem Koordinatensystem, um sich
zu überzeugen). Wenn Sie Ihren Code testen wollen,
so können Sie natürlich jederzeit das Repl-Fenster starten.
Achten Sie aber immer auf die beiden import-Befehle.
Eine Elm-App im Browser starten: elm reactor
Um aber nun die grafische Benutzeroberfläche zu sehen,
öffnen Sie eine weitere Konsole und gehen in den
Ordner. Dann rufen Sie elm reactor auf:
C:\> H:\H:\> cd PP\project-1-geometryH:\PP\project-1-geometry\>elm reactorGo to http://localhost:8000 to see your project dashboard.
Der Befehl elm reactor startet auf Ihrem Rechner
einen Webserver. Öffnen Sie nun
http://localhost:8000. Sie sehen
den Inhalt des Ordners
project-1-geometry mit drei Einträgen:
geometry, src und elm.json.
Klicken Sie auf src und dann auf
eine der Drei Dateien, die mit DemoDrop... beginnen.
Die Elm-Apps laufen nun in Ihrem Browser. Aber sie tun nicht das,
was sie soll. Die App
DemoDropTriangle.elm
zum Beispiel (Link funktioniert nur, wenn Sie den Elm-Reactor-Server wie oben beschrieben
gestartet haben) testet nicht, ob die Maus innerhalb des Dreicks liegt, sondern
ob sie links vom ältesten Punkt (dem blassgelben) liegt. Das ist
mit v.x \lt triangle.a.x natürlich einfach getestet.
Meine Dummy-Implementierung in
GeometryFunctions
dient auch nur dazu, die App zum Laufen zu bringen.
Ihre Testataufgaben
Testataufgabe Erstellen Sie eine korrekte Implementierung drei Funktionen
leftRightOrOnLine : DirectedLine -> Vector -> IntinsideTriangle : Triangle -> Vector -> BoolpointOnLineClosestTo : DirectedLine -> Vector -> Vector
Benennen Sie die Datei GeometryFunctions.elm um
nach
GeometryFunctionsVornameNachname. Dann müssen Sie die
erste Code-Zeile der Datei anpassen:
module GeometryFunctionsVornameNachname exposing (..)
Natürlich soll da nicht Vorname und
Nachname stehen, sondern Ihr Vorname und Nachname.
In den drei Apps in project-1-geometry\src\ müssen
Sie dann die Import-Zeile ändern, also
import GeometryFunctionsVornameNachname exposing (..) import GeometryFunctions exposing (..)
Schicken Sie mir dann Ihre Datei GeometryFunctionsVornameNachname.elm per Email
bis zum
3. Dezember 2023 zu.
Ändern Sie die anderen Dateien bitte nicht. Insbesondere ändern Sie
GeometryTypes.elm nicht.
In den Dateien DemoDrop...elm dürfen Sie außer dem oben beschriebenen
import-Befehl
nichts ändern. Schreiben Sie all Ihre Funktionen (inklusive weiterer Helfer-Funktionen, die
Sie ganz bestimmt
brauchen werden) in die Datei GeometryFunctionsVornameNachname.elm.
Testataufgabe Schreiben Sie Test-Cases, und zwar mindestens je drei verschiedene Test-Cases für jede der drei zu implementierenden Funktionen. Ein Test-Case sollte die Form der folgenden Funktion haben:
testTriangle1 : BooltestTriangle1 =leta : Vectora ={ x = 1, y = 0 }b : Vectorb ={ x = 0, y = 0 }c : Vectorc ={ x = 0, y = 1 }triangle : Triangletriangle ={ a = a, b = b, c = c }{--beachten Sie, dass in der Schreibweise "a = a" das erste a der Nameder Record-Variable ist; das zweite a ist der Bezeichner, den wir in Zeile 11definiert haben.--}testPoint : VectortestPoint ={ x = 0.6, y = 0.5 }-- dieser Punkt ist nicht im Dreieck drinnenin-- Test bestanden wenn die Funktion insideTriangle False zurückgibtnot (insideTriangle triangle testPoint)
Hier wird zunächst ein Dreieck definiert, dann ein Testpunkt. In diesem
Falle liegt der Testpunkt außerhalb des Dreiecks. Der Test ist also bestanden, wenn
insideTriangle den Wert False zurückgibt. Daher schreiben
wir not davor: Wir wollen, dass True bestanden und
False
nicht bestanden bedeutet. Meine Dummy-Implementierung besteht diesen Test übrigens
nicht.
Schicken Sie mir eine Datei GeometryTestVornameNachname.elm mit diesen
Test-Cases
bis zum 3. Dezember 2023 zu.
Nachtrag zur obigen Testataufgabe:
Ihre Test-Cases sollten allesamt den Wert Bool annehmen. Um zum Beispiel
pointOnLineClosestTo zu testen, machen Sie es :
testPointClosest1 : BooltestPointClosest1 =leta = {x = 0, y = 0}b = {x = 2, y = 2}c = {x = 2, y = 0}line : {from = a, to = b}trueAnswer = {x = 1, y = 1} -- was Sie als korrekte Antwort ausgerechnet habeninpointOnLineClosestTo line c == trueAnswer