In Einstieg in Visual Studio mit F# haben wir im Schnelldurchgang die ersten Schritte im Zusammenhang mit Anwendungen in F# kennen gelernt. Dabei haben wir auf Visual Studio zurückgegriffen und uns somit auf Windows oder MacOS beschränkt.
Mit Visual Studio Code bietet Microsoft hingegen einen plattformunabhängigen Editor an. In diesem Blogpost zeigen wir ähnlich wie im vorherigen Post erste Schritte in F#, diesmal in Visual Studio Code unter einem linuxbasierten Betriebssystem. Dabei erhalten wir auch Einblicke in die Bedienung mit Kommoandozeilentools, die in nahezu gleicherweise unter Windows und MacOS anwendbar sind. Dieser Artikel geht an manchen Stellen weniger ins Detail als Einstieg in Visual Studio mit F#, weshalb wir diesen Blogpost vorab empfehlen.

Installation

Unter Windows genügt es, Visual Studio zu installieren und die Entwicklung kann beginnen. Unter Linux müssen wir mehr Aufwand betreiben. Wir installieren neben Visual Studio Code noch .NET SDK, Mono und einige Plugins für Visual Studio Code. Abhängig des verwendeten Betriebssystems muss hierfür verschieden vorgegangen werden. In unserem Blogartikel verwenden wir den Nix-Paket-Manager (siehe auch Mit Nix raus aus der Versionshölle). Wir stellen unser Setup in einer nicht-invasiven Nix-Shell her. Dazu führen wir in einem Terminal

NIXPKGS_ALLOW_UNFREE=1 nix-shell -p dotnet-sdk_3 -p vscode -p fsharp -p mono

aus. Da .NET SDK nicht unter freier Lizenz steht, muss mit NIXPKGS_ALLOW_UNFREE=1 die Installation explizit erlaubt werden. Wir befinden uns nun in einer virtuellen Umgebung, die die installierten Pakete und Visual Studio Code beinhaltet. Wir erstellen einen leeren Ordner und starten Visual Studio Code:

mkdir -p ersteschritte
cd ersteschritte
code .

Als nächstes installieren wir die Erweiterungen Ionide-fsharp und C#. C# wird später für die Funktionalität der Haltepunkte und des Debuggings benötigt. In unserem Fall haben wir noch zusätzlich German Language Pack for Visual Studio Code installiert.

Projekt erstellen

Mit dem Tastenkürzel STRG + UMSCHALT + P erscheint eine Kommandozeileneingabe. Wir tippen F#: New Project. In den folgenden Abfragen wählen wir Console Application als Anwendungstyp, . (aktuelles Verzeichnis) als Projektordner und ErsteSchritte als Projektname.

Wir sehen links im Explorer einige erstellte Dateien, unter anderem die Program.fs mit einer beispielhaften Main-Methode. Unter Terminal, Neues Terminal erhalten wir eine Betriebssystemkonsole. Wir führen unser Programm erstmalig aus:

dotnet run

Wir erhalten als Rückgabe wie erwartet Hello World from F#!.

Visual Studio Code Übersicht

In der obigen Abbildung sehen wir, dass die Typsignatur von main als // string [] -> int inferiert und angezeigt wird. Fahren wir mit dem Mauszeiger über eine Definition, erhalten wir ebenfalls diese Information.

F# Interactive

Auch in Visual Studio Code können wir die interaktive Repl von F# nutzen. Dazu drücken wir in einer Zeile oder nach einem markierten Codeblock ALT + ENTER. Der entsprechende Code wird mit dem Kommandozeilentool fsharpi ausgeführt. In dem offenbleibenden F# Interactive-Fenster können wir auch eigene Anweisungen (z. B. 14 * 5;;) eingeben. Dabei muss jede Eingabe mit ;; gefolgt von ENTER beendet werden.

Debugging & Haltepunkte

Um unsere Anwendung in Visual Studio Code debuggen zu können, müssen wir einmalig eine Konfiguration anlegen. Wir gehen auf Debuggen, Debuggen starten oder drücken F5. Im erscheinenden Fenster wählen wir .NET Core. Darauf folgt die Fehlermeldung, dass keine .NET-Konfiguration angelegt werden konnte. Ebenso öffnet sich die Datei .vscode/launch.json. Hier fügen wir unsere Konfiguration ein.

Im Wert von configurations drücken wir zwischen den eckigen Klammern Enter und tippen .NET. In der erscheinenden Vorschlagsliste wählen wir .NET: Launch .NET Core Console App. Wir müssen die beiden Platzhalter <target-framework> und <project-name.dll> ersetzen und dafür die richtige Version des Frameworks und den Namen der dll-Datei ermitteln: Wenn wir im Explorer von Visual Studio Code den Ordner bin/Debug öffnen, sehen wir das verwendete Framework inklusive Version. In diesem Ordner befindet sich auch die dll-Datei. Diese heißt normalerweise gleich wie das Projekt selbst. In unserem Fall sieht die launch.json mit den beiden ermittelten Werten netcoreapp3.1 und ErsteSchritte.dll wie folgt aus:

{
    "version": "0.2.0",
    "configurations": [
       {
           "name": ".NET Core Launch (console)",
           "type": "coreclr",
           "request": "launch",
           "preLaunchTask": "build",
           "program": "${workspaceFolder}/bin/Debug/netcoreapp3.1/ErsteSchritte.dll",
           "args": [],
           "cwd": "${workspaceFolder}",
           "stopAtEntry": false,
           "console": "internalConsole"
       }
    ]
}

Wir speichern launch.json und wählen erneut Debuggen starten. Es kommt die Meldung, dass der Task build nicht gefunden wurde. In launch.json haben wir build als preLaunchTask festgelegt, da wir unser Projekt vor dem Debuggen bauen müssen.
Wir klicken auf Aufgabe konfigurieren gefolgt von Datei task.json aus Vorlage erstellen. Als Aufgabenvorlage nehmen wir .NET Core. Die automatisch erzeugte Vorlage ist für unseren Fall passend:

{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "build",
            "command": "dotnet",
            "type": "shell",
            "args": [
                "build",
                "/property:GenerateFullPaths=true",
                "/consoleloggerparameters:NoSummary"
            ],
            "group": "build",
            "presentation": {
                "reveal": "silent"
            },
            "problemMatcher": "$msCompile"
        }
    ]
}

Das Debuggen ist jetzt eingerichtet. Wir setzen in Program.fs einen Haltepunkt auf 0. Dazu klicken wir links neben die entsprechende Zeilennummer oder drücken F9 während der Cursor in dieser Zeile steht. Es erscheint ein roter Punkt am Anfang der Zeile.

Visual Studio Code Debuggen

Mit Debuggen starten wird unser Projekt gebaut und ausgeführt. Die Ausführung bleibt am Haltepunkt stehen. Links werden die Werte der bisher berechneten Variablen angezeigt. Oben erscheint eine kleine Videorecorder-Navigation, mit der wir fortsetzen, pausieren, abbrechen oder weiter springen können. Wir drücken einmal auf Weiter. Der Haltepunkt wird durchlaufen und das Programm ist beendet. Wir entfernen den Haltepunkt wieder, analog zum Hinzufügen.

Neue Datei hinzufügen

Möchten wir eine neue Datei hinzufügen, wählen wir im Explorer-Bereich Neue Datei im Kontextmenü und benennen sie MeinModul.fs. Wir geben der Datei einen Namensraum und definieren uns ein Modul mit einem abgerundeten Wert von Pi:

namespace ErsteSchritte

module MeinModul = 

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

Weiter ändern wir in der Zeile printfn der Program.fs ab zu:

printfn "%A" ErsteSchritte.MeinModul.pi

IntelliSense, die ständige Echtzeitkompilierung von F#, unterstreicht unmittelbar den Aufruf von ErsteSchritte. Wir müssen die neue Datei noch unserem Projekt hinzufügen. Dazu öffnen wir ErsteSchritte.fsproj und fügen die Zeile

<Compile Include="MeinModul.fs" />

oberhalb der gleichlautenden Zeile für Program.fs ein. Die Reihenfolge ist hier maßgebend. Die Program.fs beinhaltet den Einstiegspunkt und muss als letztes definiert sein. Ggf. müssen wir Program.fs neu öffnen. Der Aufruf ErsteSchritte.MeinModul.pi sollte nun bekannt sein. dotnet run in der Konsole ausgeführt liefert uns:

user@rechner:~/ersteschritte$ dotnet run
3.141M

Pakete installieren

Im nächsten Schritt definieren wir Testfälle. Dafür benutzen wir die Test-Frameworks NUnit und FSUnit. Um die benötigten Pakete zu installieren, bearbeiten wir ErsteSchritte.fsproj und fügen vor </Project> folgende Pakete an:

  <ItemGroup>
    <PackageReference Include="FsUnit" Version="3.8.0" />
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
    <PackageReference Include="NUnit" Version="3.12.0" />
    <PackageReference Include="NUnit3TestAdapter" Version="3.16.1" />
  </ItemGroup>

Bei der nächsten Ausführung von dotnet build werden die Pakete heruntergeladen und referenziert.

Testfall definieren und ausführen

Im Explorer von Visual Studio Code legen wir einen neuen Ordner Test an und darin die Datei MeinModulTest.fs. Wir fügen das folgende Modul mit einem Testfall für unsere Funktion MeinModul.pi ein:

namespace ErsteSchritte.Test

module MeinModulTest =
    open NUnit.Framework
    open FsUnit

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

Wir müssen die neue Datei noch als Referenz in unser Projekt aufnehmen. Wir fügen daher in ErsteSchritte.fsproj

    <Compile Include="Test/MeinModulTest.fs" />

zwischen die Zeilen mit MeinModul.fs und Program.fs ein.

Um die Tests auszuführen, führen wir den Befehl dotnet test aus. Wir erhalten in etwa:

user@rechner:~/ersteschritte 1$ dotnet test
Testlauf für "/home/td/ersteschritte/bin/Debug/netcoreapp3.1/ErsteSchritte.dll" (.NETCoreApp,Version=v3.1)
Microsoft (R) Testausführungs-Befehlszeilentool Version 16.3.0
Copyright (c) Microsoft Corporation. Alle Rechte vorbehalten.

Die Testausführung wird gestartet, bitte warten...

Insgesamt 1 Testdateien stimmten mit dem angegebenen Muster überein.
                                                                                          
Der Testlauf war erfolgreich.
Gesamtzahl Tests: 1
     Bestanden: 1
 Gesamtzeit: 0,9498 Sekunden

Fazit

Auch unter Betriebssystemen abseits von Windows können wir .NET-Core-Anwendungen mit F# entwickeln. Visual Studio Code und das Plugin Ionide bieten uns hierfür eine gute Basis. Im Vergleich zu Visual Studio unter Windows müssen wir mehr mit der Konsole arbeiten und einige Einstellungen von Hand vornehmen. Am Ende können aber Dinge, wie das Projekt zu bauen, Debuggen mit Haltepunkten oder Testfälle ähnlich einfach genutzt werden.

In einem weiteren Blogartikel werden wir die Sprache F# selbst kennen lernen. Dabei spielt es dann keine Rolle mehr, ob wir mit Visual Studio unter Windows oder wie hier mit Visual Studio Code arbeiten.