F# in Version 4 bringt bessere Integration mit C# und Co.

F# 4.0: Die Neuerungen im Überblick
Kommentare

Microsofts F# spielt im Vergleich zu C# und Visual Basic eine untergeordnete Rolle in der Welt der .NET-Sprachen. Das bedeutet allerdings nicht, dass man die Sprache als Entwickler ignorieren sollte – Zeit, einen Blick auf F# als Ganzes und auf die in Version 4.0 enthaltenen Neuerungen zu werfen.

Microsoft hat die Entwicklung des Sprachstandards an ein als F# Software Foundation bezeichnetes Standardisierungsgremium ausgelagert. Die als Non-Profit-Organisation nach US-Recht geführte Gesellschaft wacht seither über das Wohlergehen von F# – aktive Nutzer der Sprache finden auf der Webseite der Foundation diverse Möglichkeiten zur Beteiligung an der Weiterentwicklung ihres Lieblingswerkzeugs. Ein offensichtlicher Effekt dieser Neuregelung ist, dass das Voranstellen des Microsoft Namespaces in den meisten Fällen optional ist. Die beiden im Snippet gezeigten Modulladebefehle sind per Version 4.0 der Sprache gleichwertig:

open Microsoft.FSharp.Core.CompilerServices
open FSharp.Core.CompilerServices

F#-Werkzeuge

An der vierten Version der Sprache arbeiteten laut offizieller Zählung 38 Personen mit, die zu mehr als drei Vierteln keine Angestellten von Microsoft waren. Ein wichtiges Werkzeug bei der Koordinierung der Arbeiten war der bereitstehende Bug- und Vorschlags-Tracker, in dem die Geschichte der in diesem Artikel besprochenen Funktionen im Detail betrachtet werden kann.

Trotz dieses „Outsourcings“ steht man in Redmond nach wie vor voll hinter F#. Visual Studio 2015 bringt – auch in der kostenlosen Community Edition – alles mit, was man zur Entwicklung leistungsfähiger F#-basierter Applikationen braucht. Leider werden die F#-Tools von Haus aus nicht deployt: Dazu müssen Sie den Projektassistenten öffnen und in die Rubrik F# wechseln. Wenn Visual Studio dort den in Abbildung 1 gezeigten Downloadlink einblendet, ist ein Doppelklick erforderlich. Er startet den in die IDE integrierten Nachladeassistenten, der die F#-Werkzeuge automatisch auf Ihre Maschine holt.

hanna_fsharp_1

Abb. 1: Downloadlink in Visual Studio

F# 4.0 bringt intelligenteren Präprozessor

C-Compiler bearbeiten den Code vorher mit einem auf Textebene arbeitenden Werkzeug, das – unter anderem – für Kopierschutz und konditionale Kompilation eingesetzt werden kann. Wegen seiner Komplexität gilt der Präprozessor als Hürde für Quereinsteiger und ist in „modernen Sprachen“ wie Java nur teilweise implementiert.

F# brachte bis zur Version 4.0 nur einen rudimentären Präprozessor mit, der keine konditionalen Fähigkeiten besaß. Das bedeutet, dass Entwickler nur auf das Vorhandensein bestimmter Marker prüfen konnten – kombinatorische Abfragen nach dem Schema „wenn a UND b ODER c“ waren bisher nicht möglich.

Die vierte Version kippt diese Beschränkung. Erstellen Sie eine neue F#-Kommandozeilenapplikation namens SUSFSPraeprozessor; der Inhalt von Program.fs wird nach dem Schema in Listing 1 angepasst.

[<EntryPoint>]
let main argv = 
  #if BUEMMLER ||(!ZAEHLER && INTEROTTAMENTE)
  printfn "Aktiviere unterbrechungsfreie Funktionsanpassung"
  #else
  printfn "Funktionsgenerator bereit!"
  #endif
  printfn "%A" argv
  0 // return an integer exit code

Das Skript in Listing 1 deklariert eine Methode namens main, die als Parameter ein Feld namens argv entgegennimmt. Sie ruft je nach gesetzten Präprozessorflaggen printfn mit verschiedenen Parametern auf, um danach das Parameterarray auszugeben und 0 an das Betriebssystem zu retournieren.

Interessanterweise bietet F# keine Möglichkeit zur Inline-Deklaration von Präprozessorkonstanten: Das klassische #define ist laut der bereitstehenden Referenz nicht implementiert. Stattdessen muss man die gewünschten Flags unter Project | <Name> Properties | Build | Conditional compilation symbols eingeben. Visual Studio übergibt die dort eingegebenen Werte direkt an die F#-Laufzeitumgebung.

Als – zugegebenermaßen exotische – Alternative zu dieser Vorgehensweise bietet sich die Nutzung von fsi.exe an. Der dynamische Interpreter für F# kann auch aus einem anderen F#-Programm heraus aufgerufen werden und nimmt auf der Kommandozeile ebenfalls Parameter entgegen. Dazu müssen wir unsere Beispiellösung um eine weitere Datei ergänzen. Visual Studio bietet im Dateieinfügungsassistenten die in Abbildung 2 gezeigten Vorlagen an.

hanna_fsharp_2

Abb. 2: F#, in drei Varianten

.fs-Dateien enthalten normalen F#-Code, der während der Kompilation des Projekts in die Solution eingearbeitet wird. .fsx-Files sind Skripte, die für die direkte Ausführung per FSI vorgesehen sind. Zu guter Letzt gibt es mit .fsi-Dateien die Möglichkeit zur Beschreibung von „Interfaces“ in .fs-Files – weitere Daten dazu stehen hier bereit.

Der bisherige Programmcode des Zählers wird nun in zaehler.fsx ausgelagert. Da fsi Skripte „direkt“ von oben nach unten abarbeitet, ist keine Deklaration eines Einsprungpunkts notwendig – argc und argv gibt es natürlich auch nicht mehr (Listing 2).

#if BUEMMLER ||(!ZAEHLER &amp;amp;amp;&amp;amp;amp; INTEROTTAMENTE)
printfn "Aktiviere unterbrechungsfreie Funktionsanpassung"
#else
printfn "Funktionsgenerator bereit!"
#endif
0 // return an integer exit code

Program.fs wird nun mit dem Code aus Listing 3 ausgestattet.

open System.Diagnostics
[&amp;amp;lt;EntryPoint&amp;amp;gt;]
let main argv = 
  let procStart = new ProcessStartInfo("C:\\Program Files (x86)\\Microsoft SDKs\\F#\\4.0\\Framework\\v4.0\\fsi.exe", "--define:BUEMMLER \"c:\\users\\tamhan\\documents\\visual studio 2015\\Projects\\SUSFSPraeprozessor\\SUSFSPraeprozessor\\zaehler.fsx\"")
  procStart.WorkingDirectory &amp;amp;lt;- "C:\\"
  let res = Process.Start(procStart)

fsi.exe hat die unangenehme Eigenschaft, zur Laufzeit auf eine Bibliothek zurückzugreifen, die von der in Visual Studio enthaltenen F#-Umgebung gesperrt wird. fsi ist somit nur dann lauffähig, wenn wir einen alternativen Arbeitspfad einschreiben – dass uns das zum Angeben voll qualifizierter Pfade zwingt, ist eine unangenehme Nebeneigenschaft.

Von C# oder VB umsteigende Entwickler wundern sich zudem über die Parameterübergabe: Das Setzen von Variablen erfolgt durch einen nach links zeigenden Pfeil, der den Wert des Strings in WorkingDirectory schreibt.

Ob diese Lösung in der Praxis gangbar ist, hängt vom Bedarf für Debugger-Unterstützung (nicht vorhanden) und der Empfindlichkeit des zu verarbeitenden Quellcodes ab. Wenn dieser nicht mit den Endverbrauchern geteilt werden darf, ist die dynamische Ausführung per fsi nicht realisierbar. Das bedeutet allerdings nicht, dass das Startskript nicht in der IDE Einsatz finden kann.

Schnell und überall: Datenzugriff mit Entity Framework Core 2.0

Dr. Holger Schwichtenberg (www.IT-Visions.de/5Minds IT-Solutions)

C# 7.0 – Neues im Detail

Christian Nagel (CN innovation)

Änderungen bei der Prüfung von Code

Fehler in Einheitenkonvertierung sind die Achillesferse von Elektronikern. Diverse Projekte – darunter sogar eine richtig teure Marssonde – scheiterten daran, dass verschiedene Teams mit ihren eigenen Einheitensystemen arbeiteten und die Parameter nicht miteinander austauschten. Als Beispiel dafür dienen die drei folgenden Zeilen, die Widerstandseinheiten deklarieren:

[&amp;amp;lt;Measure&amp;amp;gt;] type ohm
[&amp;amp;lt;Measure&amp;amp;gt;] type kohm = ohm^3
[&amp;amp;lt;Measure&amp;amp;gt;] type mohm = kohm^3

F# 4.0 bessert an dieser Stelle insofern nach, als dass die Exponenten nun keine „geraden“ Integers mehr sein müssen. Microsoft demonstriert das anhand der Definition der auf Robert Clark Jones zurückzuführenden Jones – einer Einheit, die zur Beschreibung der Empfindlichkeit eines Fotodetektors zum Einsatz kommt. Die im Folgenden gezeigte Deklaration ist erst seit F# 4.0 legal:

open FSharp.Data.UnitSystems.SI.UnitSymbols
[&amp;amp;lt;Measure&amp;amp;gt;] type cm
[&amp;amp;lt;Measure&amp;amp;gt;] type Jones = cm Hz^(1/2)/W

Achten Sie darauf, dass die Prüfung des Codes anhand des Einheitensystems nur während der Kompilation erfolgt. Zur Laufzeit sieht das System nur Floats ohne Einheiteninformationen – es ist nicht möglich, eine Funktion zum Ausgeben von Wert samt Einheit zu konstruieren.

Ein klassisches Beispiel für einen von F# auffindbaren Fehler wäre folgende Routine, die einem Widerstand einen Wert in Zentimeter zuzuweisen sucht – der Compiler moniert r1 während der Zusammenfügung des Programms:

[&amp;amp;lt;EntryPoint&amp;amp;gt;]
let main argv = 
  let r1=2&amp;amp;lt;cm&amp;amp;gt;
  let r2=2&amp;amp;lt;ohm&amp;amp;gt;
  let wert ohm = r1+r2
  0 // return an integer exit code

Wer mit Einheiten behafteten Code per print* ausgeben wollte, musste die Einheiteninformationen bisher manuell „strippen“. F# bessert an dieser Stelle nach – eine kleine Änderung, die sich aber  massiv auf die Länge des zu schreibenden Codes auswirkt:

let main argv = 
  let r1=2&amp;amp;lt;ohm&amp;amp;gt;
  let r2=2&amp;amp;lt;ohm&amp;amp;gt;
  let wert : int&amp;amp;lt;ohm&amp;amp;gt; = r1+r2
  printfn "%d" wert
  //statt printfn "%d" (int wert) 

Höherwertige Arrays von Compiler und Runtime bekannt

F# beschränkte die maximale Dimensionszahl von Arrays bisher auf vier, was insbesondere beim Aufrufen externer Bibliotheken für Ärger und Abstürze sorgte. Die vierte Version der Sprache bessert an dieser Stelle insofern nach, als dass Compiler und Runtime nun von der Existenz höherwertiger Arrays wissen. Das bedeutet, dass die Elemente nicht mehr zu Fehlverhalten führen – die in F# implementierten Arrayklassen wurden indes noch nicht an die neue Situation angepasst, weshalb der Zugriff auf die darin enthaltenen Informationen derzeit nicht möglich ist.

F# kennt auch Sequenzen

F# kennt neben Arrays und Listen auch Sequenzen: Es handelt sich dabei um eine Art virtuelles Array, dessen Inhalte erst dann erzeugt werden, wenn ihre Werte erforderlich sind. Das ist insbesondere zur speichersparenden Realisierung von Look-up-Tabellen hilfreich, die die Runtime so erst bei Bedarf füllt.

Jede der drei Speicherstrukturen wird von einem Modul begleitet, das häufige Manipulationsoperationen mit vorgefertigtem Code unterstützt. Microsoft nutzte F# zur Erweiterung und Finalisierung dieser Module – Abbildung 3 zeigt den aktuellen Stand.

hanna_fsharp_3

Abb. 3: F#-Datenstrukturen enthalten jede Menge Intelligenz (Quelle: Microsoft)

Blaue Felder sind seit F# 3.0 vertreten, während ihre grünen Kollegen mit F# 4.0 implementiert wurden. Weiß hinterlegte Felder sind „intentionally left blank“– es dürfte sich hierbei um Funktionen handeln, die Microsoft auf keinen Fall implementieren will (oder deren Implementierung für die jeweilige Speicherstruktur sinnfrei bzw. nicht möglich wäre).

Eine weitere kleine Erleichterung ist die Implementierung der bei Arrays schon länger verfügbaren Slicing-Syntax. Sie erleichtert das Errichten von Sublisten – unser Snippet zeigt die Erzeugung einer Basisliste und zweier davon abgeleiteter Unterlisten:

let feld = [2 .. 20]
let unterfeld=feld.[2..4]
let unterfeld2=feld.[3..]

Feld enthält hierbei 19 Elemente, die den Bereich von zwei bis inklusive 20 abdecken. Das Aufrufen der .[]-Methode mit dem Zweipunktoperator – das versehentliche Eintippen von drei Punkten dürfte der häufigste Vertipper der Welt sein – bevölkert Unterfeld mit den Werten 4|5|6. unterfeld2 ist sodann ein Feld, das mit dem dritten Feld von feld beginnt.

Quasi nebenbei erfolgte eine Optimierung der Hash-Vergleichstabellen. Microsoft verspricht davon massive Performancesteigerungen in distinct, distinctBy, groupBy und damit vergleichbaren Funktionen, die auf Collection-Ebene arbeiten.

Asynchrone Downloads

Das Herunterladen von Informationen nimmt Zeit in Anspruch: Schon unter Palm OS nutzten Entwickler die Methoden der asynchronen Programmierung zur Reduktion von Wartezeiten.

F# 4.0 erleichtert Entwicklern die Arbeit durch zwei neue Erweiterungen der in FSharp.Control.WebExtensions enthaltenen WebClient-Klasse. DownloadFileAsync ist dabei für das Herunterladen in eine lokale Datei zuständig:

member DownloadFileAsync : 
  address:Uri *
  fileName:string -&amp;amp;gt; unit

DownloadDataAsync unterscheidet sich dadurch insofern, als dass die Methode stattdessen ein Byte-Array mit den eingesammelten Informationen anbietet:

member DownloadDataAsync : 
  address:Uri -&amp;amp;gt; unit

Achten Sie darauf, dass Microsoft die Methoden anfangs als AsyncDownloadFile und AsyncDownloadData bezeichnet hat. Wer für die Preview-Versionen von F# 4.0 geschriebenen Code bearbeitet, sollte bei Bedarf auf Suchen und Ersetzen zurückgreifen.

Raschere Vergleiche von Strukturen und Objekten

Der Vergleich von Strukturen und/oder Objekten ist seit jeher ein interessantes Betätigungsfeld für Theoretiker und Philosophen: Es ist eine berechtigte Frage, ob zwei mit identischen Members ausgestattete Objektinstanzen identisch (gleiche Werte) oder verschieden (verschiedene Adressen) sind.

Als funktionale Sprache implementierte F# bisher „große“ Vergleiche anhand der Member-Variablen: Diese aus technischer Sicht sinnvolle Vorgehensweise ist für eine .NET-Sprache ungünstig, weil der Rest der Programmierumgebung samt diverser Overloads von einfacheren Vergleichen ausgeht. In F# 4.0 haben Entwickler die Möglichkeit, die zu verwendende Vergleichsmethode über das Laden eines speziellen Moduls anzupassen. Wer seinen Code mit der Deklaration open NonStructuralComparison ausstattet, erntet immense Performancegewinne – Abbildung 4 zeigt die Ergebnisse eines von Microsoft durchgeführten Benchmarks.

hanna_fsharp_4

Abb. 4: Einfachere Vergleiche führen zu wesentlich höherer Gesamtperformance (Quelle: Microsoft)

Print in Style

Die in C, C++ und Co. vertretene print*-Funktionsfamilie arbeitet mit Formatstrings: Der Entwickler muss im ersten Schritt das Format der zu verwendenden Daten festlegen, die sodann nacheinander aus dem Stack geholt werden. F# bietet mit StructuredFormatDisplay seit längerer Zeit eine Möglichkeit zur Spezifizierung eines hausgemachten Formatstrings, der zur Anzeige einer bestimmten Klasse eingespannt werden kann. Die Funktion erfuhr in der vierten Version der Sprache massive Erweiterungen im Bereich der Syntax – so ist es nun möglich, mehrere Werte zu verwenden und sogar auf {} zurückzugreifen.

Zur Demonstration dieses Features dient ein kleines Snippet, das das formatierte Ausgeben eines Datentyps anhand von StructuredFormatDisplay vorführt:

[&amp;amp;lt;StructuredFormatDisplay("{Name} wiegt {Weight}. Hat Bewaffnung: {Armed}")&amp;amp;gt;]
type MyPzr={
  Name: string
  Weight: float
  Armed: bool
}
[&amp;amp;lt;EntryPoint&amp;amp;gt;]
let main argv = 
  printfn "%A" {Name="Panzer"; Weight=300.0; Armed=true}

Von früheren Versionen von F# umsteigende Entwickler müssen sich hier an eine kleine Änderung gewöhnen: Das Verwenden der Zeichen { und } im Formatstring setzt fortan die Verwendung eines Escape Slashs voraus.

Bessere Integration mit C# und Co.

Eines der Versprechen der .NET-Runtime war, dass verschiedene Programmiersprachen gemeinsam auf die Services der Laufzeitumgebung zugreifen konnten. Zudem sollten Module besser untereinander austauschbar sein. Dieses hehre Ziel setzt auf Seiten der verwendeten Sprachen „Funktionsparität“ voraus – das weiter oben beschriebene Problem mit Arrays erwies sich als Hindernis für den Zugriff auf Methoden, die derartige Werte verwendeten oder zurückgaben.

Generics waren in C# von Anfang an Teil der Sprache: Es war für Entwickler kein Problem, von mehreren, verschieden typisierten Versionen eines generischen Interface zu erben. In F# war das bisher nicht möglich, weil die Typstruktur des Compilers damit nicht zurechtkam – derartige Klassen konnten aus F# heraus nicht einmal angesprochen werden.

Die vierte Version hebt diese Beschränkungen teilweise auf. Das bedeutet, dass Sie ab sofort auch C#-Basisklassen verwenden können, die beispielsweise IComparable<int> und IComparable<string> realisieren. In F# selbst sind derartige Konstrukte noch nicht erlaubt: Microsoft empfiehlt als Alternative dazu die Errichtung einer zweistufigen Typenhierarchie, die die benötigten Interfaces stufenweise einpflegt.

Diese auf den ersten Blick immens komplexe Vorgehensweise ist einfach. Beginnen wir mit einem Hilfstyp namens KlassenHelfer, dessen Aufgabe die Implementierung von IComparable für Integerwerte darstellt:

open System
type KlassenHelfer(va:int)=
  interface IComparable&amp;lt;int&amp;gt; with
    member __.CompareTo(other)=va.CompareTo(other)

Im nächsten Schritt folgt die Erstellung des eigentlichen Typs, der vom soeben implementierten Helfer erbt und zusätzlich eine weitere Variante von IComparable einbindet. Aus Platzgründen können wir hier nicht näher auf die verwendete Syntax eingehen – es wäre illusorisch, F# umfassend in einem Artikel eines Fachmagazins beschreiben zu wollen:

type Klasse(va:int)=
  inherit KlassenHelfer(va)
  interface IComparable&amp;lt;string&amp;gt; with
    member __.CompareTo(other)=(string va).CompareTo(other)

Der Lohn dieser mehrstufigen Vorgehensweise ist eine Klasse, die beide Permutationen des gegenständlichen Interface realisiert. In ferner Zukunft dürfte Microsoft diese Beschränkung aufheben – der Workaround sollte allerdings auch danach ohne Probleme funktionieren.

Eine weitere Verbesserung betrifft die Interaktion mit nullable-Typen des .NET-Basis-Frameworks. Das Optionsobjekt wurde um vier Methoden namens toNullable, ofNullable, ofObj und toObj erweitert; in Operators finden sich mit tryUnbox und isNull ebenfalls zwei hilfreiche Werkzeuge.

Visual Studio mit verbessertem Tooling

Natürlich geht eine neue Version von Visual Studio mit Änderungen am Tooling einher: IntelliSense liefert bei der Erstellung neuer Objektinstanzen nun bessere Vorschläge. Eine weitere wichtige Änderung betrifft Lösungen mit mehreren Projekten: Frühere Versionen von Visual Studio überwachten die einzelnen Elemente nicht, weshalb regelmäßig komplette Rekompilationen notwendig waren.

Visual Studio 2015 bindet F#-Code in den für andere Sprachen seit längerer Zeit verfügbaren Up-to-Date-Checker ein, was Rekompilationen in vielen Fällen vermeidet und so die Performance wesentlich erhöht. Ein weiteres Gimmick zur Performanceerhöhung ist, dass sich der Debugger nun auch an in REPL lebende Skripte anschließen lässt – dazu stehen Kontextmenüs im Editor und im F#-Interactive-Fenster zur Verfügung.

F# kennt aufgrund seiner Herkunft aus dem akademischen Bereich keine Möglichkeit, um Code in Ordner zu unterteilen. Zur Lösung dieses Problems bietet die F# Software Foundation die Visual F# Power Tools an, die über NuGet heruntergeladen werden können. Visual Studio 2015 ist auf die Arbeit mit dieser Erweiterung optimiert – im Vergleich zur Vorgängerversion verspricht Microsoft die Behebung von Bugs und die Integration von nicht näher spezifizierten „Verbesserungen des Zusammenspiels“.

Zu guter Letzt erfolgte eine Optimierung des in fsc.exe implementierten Verhaltens des Garbage Collectors. Die Umstellung auf GCLactencyMode.Batch sorgt dafür, dass der Garbage Collector den Compiler bei Bedarf anhalten und so effizienter auf Abfalljagd gehen kann. Diese auf den ersten Blick kontraproduktiv klingende Änderung sorgt in der Praxis für rund zehn Prozent mehr Performance: Bei Compilern ist Echtzeitfähigkeit nun mal von untergeordneter Bedeutung.

Fazit

Auch wenn F# wegen seiner eher geringen Verbreitung in kommerziellen Projekten nur selten eine Rolle spielen dürfte – die Beschäftigung mit neuen Programmiersprachen und den dazugehörenden Paradigmen zahlt sich schon aus didaktischen Gründen aus. F# kombiniert Elemente aus der funktionalen, der imperativen und der objektorientierten Programmierung. Wer sich mit der Sprache befasst, sammelt wertvolle Erkenntnisse in drei interessanten Feldern der IT.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -