F# (F Sharp) ist eine von Microsoft entwickelte funktionale Programmiersprache im .NET-Universum. Die Syntax erinnert sehr stark an OCaml. Microsoft bietet mit Visual Studio eine komplette Entwicklungsumgebung an, die neben F# auch mit vielen weiteren Sprachen zurecht kommt. In diesem Blogpost sehen wir uns erste Schritte im Zusammenspiel von F# mit Visual Studio an und erläutern einige ungewöhnliche Eigenarten. Erklärungen zur Syntax von F# lassen wir weitestgehend außer acht. Dafür verweisen wir auf einen zeitnah erscheinenden Blogartikel zum Kennenlernen von F#.

Bevor es los geht

Visual Studio ist in der vollumfänglichen Variante nur für Windows verfügbar und ist nicht zu verwechseln mit Visual Studio Code. Visual Studio ist in der Community Edition kostenlos installierbar. Für MacOS ist Visual Studio für Mac mit annährend gleichem Funktionsumfang verfügbar.
Wir wählen bei der Installation das zusätzliche Paket (Workload) .NET Desktopentwicklung aus. F# ist in Visual Studio direkt enthalten.

Wir öffnen Visual Studio und wählen Neues Projekt erstellen auf dem Willkommensbildschirm. Im Suchfeld geben wir F# ein und wählen Konsolenanwendung (.NET Framework). Um das Projekt zu erstellen, vergeben wir im nächsten Schritt noch den Namen ErsteSchritte und wählen einen Speicherort aus.

Übersicht

Das folgende Bild zeigt dem Aufbau in unserem neu angelegten Projekt.

Visual Studio Übersicht

Links oben (1) ist die beispielhafte Program.fs-Datei geöffnet. Rechts (2) sehen wir den Projektmappen-Explorer. In unserer Projektmappe (Workspace) ErsteSchritte haben wir momentan nur das gleichnamige Projekt (Solution) ErsteSchritte. In einer Projektmappe können verschiedene Projekte angelegt werden. Dabei kann jedes Projekt andere Abhängigkeiten und Einstellungen haben (siehe Abschnitt Pakete mit NuGet installieren).

Unten (3) sehen wir F# Interactive, die Konsole oder Repl, in der wir direkt F#-Anweisungen ausführen können.

F# Interactive

Um die F# Interactive-Konsole aufzurufen, können wir eine Codeanweisung, z. B. die 0 aus Program.fs, markieren und im Kontextmenu Interaktiv ausführen wählen. Die Anweisung wird ausgeführt und sollte in unserem Beispiel zu

val it : int = 0

führen. Da wir nur einen Wert eingegeben haben, bindet F# Interactive diesen an die mutierbare Variable it. Wir können in späteren Anweisungen den Bezeichner it verwenden, um den Wert abzurufen, analog zur ANS-Funktionalität bei Taschenrechnern, die den letzten errechneten Wert (Answer) repräsentiert.

Im Fenster von F# Interactive lassen sich auch direkt Anweisungen eintippen, z. B.:

4 + 5;;

Dabei müssen wir jede Anweisung mit ;; beenden, um sie mit Enter ausführen zu können.

Program.fs

Program.fs ist der Startpunkt unserer Anwendung. Innerhalb dieser Datei sehen wir die Markierung [<EntryPoint>], welche die Funktion mit dem Namen main als initialen Aufruf festlegt. Wir ändern diese Funktion wie folgt ab:

[<EntryPoint>]
let main argv =
    printfn "%A" "Hallo zusammen!"
    0 // return an integer exit code

Wenn wir nun auf den Button Starten (rechts neben den Einstellungen Debug und Any CPU) gehen, sollte sich ein externes Konsolenfenster öffnen, welches aber sofort wieder verschwindet. Zum Starten können wir alternativ auch das Tastenkürzel F5 benutzen.

Um die Ausgabe sehen zu können, kann die Ausführung auch mit offenbleibendem Terminal erfolgen: Dazu benutzen wir das Tastenkürzel STRG + F5.

Haltepunkte

Alternativ können wir die Zeile mit der 0 markieren und im Kontextmenü Haltepunkt, Haltepunkt einfügen auswählen. Wenn wir nun auf Start gehen, unterbricht Visual Studio vor dieser Stelle die Ausführung. Als Folge davon können wir die Ausgabe in der Konsole betrachten, indem wir das Konsolenfenster in den Vordergrund bringen. Sobald wir auf den Button Weiter drücken, wird der Haltepunkt durchlaufen und unser Beispielprogramm ist beendet. Durch Rechtsklick auf den links sichtbaren Haltepunkt und Haltepunkt löschen können wir diesen wieder entfernen.

Haltepunkte eignen sich später sehr gut zum Debuggen. Hält das Programm an einem Haltepunkt an, wird der Wert aller verfügbarer Variablen angezeigt, wenn man die Maus darüber hält.

Projektmappen-Explorer

Im obigen Übersichtsbild sehen wir rechts den Projektmappen-Explorer. Neben einigen Konfigurationsdateien wird unsere Program.fs aufgelistet. Wir können durch Rechtsklick auf unser Projekt, mit Hinzufügen, Neues Element, Quelldatei eine neue Modul-Datei hinzufügen. Wir nennen diese MeinModul.fs.

Die Datei erscheint im Projektmappen-Explorer an letzter Stelle. Die Dateien werden der Reihenfolge nach geladen. Da wir später MeinModul aus der Hauptmethode in Program.fs aufrufen wollen, müssen wir MeinModul.fs nach oben schieben. Dazu klicken wir auf die Datei und navigieren sie mit ALT + PFEIL OBEN nach oben. Drag und Drop stellt eine andere Funktionalität dar (duplizieren der Datei). Dieses Verhalten soll evtl. in einer zukünftigen Visual Studio Version geändert werden.

Live-Kompilierung mit IntelliSense

Wir bearbeiten unsere neu erstellte Modul-Datei und definieren die Kreiszahl. Dabei fügen wir zuerst am Ende von module MeinModul ein =-Zeichen an.

module MeinModul = 

    /// Pi als abgerundete Dezimalzahl
    let pi : decimal = 
        3.141M

Wenn wir nun auf Starten gehen, erhalten wir folgende Fehlermeldung: Dateien in Bibliotheken oder Anwendungen mit mehreren Dateien müssen mit einer Namespace- oder Moduldeklaration beginnen. […] Nur in der letzten Quelldatei einer Anwendung darf eine solche Deklaration ausgelassen werden.

Visual Studio Fehlermeldung Namespace

Wir fügen der MeinModul.fs als erste Zeile

namespace ErsteSchritte

hinzu. Das Programm startet jetzt ohne Fehlermeldung. Eventuell bleibt die vorherige Meldung noch in der Fehlerliste sichtbar. Um dies zu lösen, klicken wir rechts auf ErsteSchritte im Projektmappen-Explorer und wählen zuerst Projekt entladen und anschließend Projekt erneut laden. Dieses Verhalten hängt mit der neu erstellten MeinModul.fs und der dynamischen Generierung/Überprüfung mit IntelliSense zusammen. IntelliSense kompiliert ständig im Hintergrund, um so Meldungen über falschen Syntax, nicht passende Typen oder sonstige Fehler aufmerksam zu machen. Entsprechende Stellen werden rot unterstrichen. Zusätzlich sind sie in der Fehlerliste sichtbar, sofern dort der Modus Nur IntelliSense oder Erstellen + IntelliSense gewählt ist. Außerdem bietet uns IntelliSense die Möglichkeit der Autovervollständigung.

Aufruf einer Funktion

In Program.fs ändern wir die Ausgabe von „Hallo zusammen!“ auf den Aufruf von pi ab. Dafür ersetzen wir die Zeile mit printfn durch

printfn "%A" ErsteSchritte.MeinModul.pi

Durch Angabe des Namensraums, gefolgt von der Modulbezeichnung, können wir alle nicht privaten Funktionen und Werte aufrufen. Zum Test können wir in MeinModul.fs let pi durch let private pi ersetzen. In der Program.fs meldet uns nun IntelliSense, dass wir auf diesen Wert nicht zugreifen können. IntelliSense erkennt dateiübergreifende Änderung erst nachdem man die Datei gespeichert hat.

Test-Infrastruktur

Da auch bei der Rundung von Pi Fehler entstehen können, erstellen wir unmittelbar unseren ersten Testfall. Wir legen uns im Projektmappen-Explorer einen Ordner Test an und erstellen darin die Quellcodedatei MeinModulTest.fs. Wir schieben mit ALT + PFEIL UNTEN die Program.fs wieder an die letzte Stelle. Die dort markierte main-Methode muss immer in der letzten Datei stehen. Falls erneut die Meldung bzgl. Namespace- oder Moduldeklaration erscheint, müssen wir wie oben beschrieben unser Projekt ErsteSchritte ent- und erneut laden.

Als Test-Framework benutzen wir im Folgenden NUnit und FsUnit. Diese Pakete installieren wir mit NuGet.

Pakete mit NuGet installieren

Im Projektmappen-Explorer können wir über das Kontextmenü eines Projekts (Solution) oder im Kontextmenü der Projektmappe (Workspace) den Paketmanager aufrufen (NuGet Pakete verwalten bzw. NuGet Pakete für Projektmappe verwalten). Je nachdem was wir wählen, installieren wir das Paket für ein einzelnes Projekt oder für die ganze Projektmappe und damit sichtbar für alle Projekte.

Um ein Paket zu installieren, suchen wir ein Paket im Reiter Durchsuchen. Wir setzen rechts den Haken vor Projekt und gehen auf Installieren. Nach Bestätigung einer Meldung wird das Paket installiert. Wir installieren die Pakete:

  • NUnit
  • NUnit3TestAdapter
  • FsUnit

NUnit stellt die Grundfunktionalität des Testframeworks dar. NUnit3TestAdapter ist ein sogenannter Test-Adapter für Visual Studio. Damit lassen sich alle Tests mit dem Test-Explorer von Visual Studio verwalten. FsUnit stellt eine Reihe von Methoden für die Forumulierung von Prüfungen bereit.

Testfall definieren und ausführen

Zurück in unserer noch leeren MeinModulTest.fs-Datei legen wir erneut einen Namensraum und die Modulstruktur fest. Weiter binden wir unsere zuvor installierten Test-Tools ein:

module MeinModulTest =
    open NUnit.Framework
    open FsUnit

Ein Testfall besteht aus der Markierung [<Test>] und einer Funktionsdefinition. Dabei gibt der Funktionsname den Testnamen an. Damit dieser auch Leerstellen und beliebige Groß-/Kleinschreibung enthalten kann, fassen wir diesen in doppelte Backticks `` ein. Zum Beispiel:

[<Test>]
let ``Defintion of Pi`` () =
   ErsteSchritte.MeinModul.pi |> should equal 3.141M

Um den Testfall auszuführen, gehen wir in den Test-Explorer (Ansicht, Test-Explorer) und drücken auf den Vorspulen-Pfeil Alle Tests ausführen. Damit wird das Projekt gebaut und unser Testfall wird vom Test-Explorer gefunden. Drücken wir erneut Alle Tests ausführen, wird er ausgeführt und sollte auch bestanden werden.

Visual Studio Testfall & Testexplorer

Die Tests werden nach Projekten, Namensräume und Modulen gruppiert. In unserem Fall scheint das übermäßig komplex zu sein. Bei mehreren hundert Tests können später, insbesondere durch die Verwendung von Namensräumen, einzelne Test-Module weiter gruppiert werden. Innerhalb eines Moduls werden die Tests nach ihrer Position in der Datei sortiert.

Fazit

Visual Studio bietet eine Menge Funktionen zur Entwicklung mit F# an. Dabei lassen sich einige Eigenheiten, wie die Sortierung der Projektdateien, nicht leugnen. In diesem Artikel haben wir einen schnellen Rundgang über die Projektstruktur, die Paketverwaltung und das Testen vollzogen. In einem folgenden Blogartikel gehen wir näher auf F# ein. Dann machen wir uns mit der Syntax der Sprache vertraut.