Neues in F# 6

In F# 6 wurden einige Verbesserungen an F# und F# Interactive vorgenommen. Die Version wird mit .NET 6 veröffentlicht.

Sie können das neueste .NET SDK über die .NET-Downloadseite herunterladen.

Erste Schritte

F# 6 ist in allen .NET Core-Distributionen und Visual Studio-Tools verfügbar. Weitere Informationen finden Sie unter Erste Schritte mit F#.

task {…}

F# 6 enthält native Unterstützung für die Erstellung von .NET-Tasks im F#-Code. Sehen Sie sich als Beispiel den folgenden F#-Code an, mit dem ein .NET-kompatibler Task erstellt wird:

let readFilesTask (path1, path2) =
   async {
        let! bytes1 = File.ReadAllBytesAsync(path1) |> Async.AwaitTask
        let! bytes2 = File.ReadAllBytesAsync(path2) |> Async.AwaitTask
        return Array.append bytes1 bytes2
   } |> Async.StartAsTask

Mit F# 6 kann dieser Code wie folgt umgeschrieben werden.

let readFilesTask (path1, path2) =
   task {
        let! bytes1 = File.ReadAllBytesAsync(path1)
        let! bytes2 = File.ReadAllBytesAsync(path2)
        return Array.append bytes1 bytes2
   }

Die Taskunterstützung war für F# 5 über die hervorragenden Bibliotheken „TaskBuilder.fs“ und „Ply“ verfügbar. Das Migrieren von Code zur integrierten Unterstützung sollte eine unkomplizierte Angelegenheit sein. Es gibt jedoch einige Unterschiede: Namespaces und Typrückschlüsse unterscheiden sich geringfügig zwischen der integrierten Unterstützung und diesen Bibliotheken, und möglicherweise sind einige zusätzliche Typanmerkungen erforderlich. Bei Bedarf können Sie diese Communitybibliotheken mit F# 6 weiterhin verwenden, wenn Sie explizit darauf verweisen und in jeder Datei die richtigen Namespaces öffnen.

Die Verwendung von task {…} ähnelt der Verwendung von async {…} sehr. Die Verwendung von task {…} bietet mehrere Vorteile gegenüber der Verwendung von async {…}:

  • Der Mehraufwand von task {...} ist geringer. Dadurch wird möglicherweise die Leistung in heißen Codepfaden verbessert, in denen die asynchrone Arbeit schnell ausgeführt wird.
  • Das Debuggen in Einzelschrittausführung sowie Stapelüberwachungen für task {…} sind besser.
  • Die Interoperabilität mit .NET-Paketen, die Tasks erwarten oder erzeugen, ist einfacher.

Wenn Sie mit async {…} vertraut sind, sollten Sie einige Unterschiede beachten:

  • task {…} führt den Task sofort bis zum ersten Wartepunkt aus.
  • task {…} gibt ein Abbruchtoken nicht implizit weiter.
  • task {…} führt keine impliziten Abbruchprüfungen durch.
  • task {…} unterstützt keine asynchronen Endeaufrufe. Das bedeutet, dass die rekursive Verwendung von return! .. zu Stapelüberläufen führen kann, wenn keine asynchronen Wartevorgänge dazwischen liegen.

Im Allgemeinen sollten Sie in neuem Code task {…} gegenüber async {…} bevorzugen, wenn Interoperabilität mit .NET-Bibliotheken erforderlich ist, die Tasks verwenden, und wenn Sie keine asynchronen Codeendeaufrufe und keine implizite Weitergabe von Abbruchtoken nutzen. Im vorhandenen Code sollten Sie erst zu task {…} wechseln, nachdem Sie Ihren Code überprüft haben, um sicherzustellen, dass Sie die zuvor erwähnten Merkmale von async {…} nicht benötigen.

Dieses Feature implementiert F# RFC FS-1097.

Einfachere Indizierungssyntax mit expr[idx]

F# 6 erlaubt die Syntax expr[idx] für die Indizierung und das Slicing von Sammlungen.

Bis einschließlich F# 5 verwendete F# expr.[idx] als Indizierungssyntax. Das Zulassen der Verwendung von expr[idx] basiert auf dem wiederholten Feedback von Benutzern, die F# erlernen oder zum ersten Mal sehen, dass eine Indizierung mit Punktnotation als unnötige Abweichung von branchenüblichen Verfahren empfunden wird.

Dies ist kein Breaking Change, da bei Verwendung von expr.[idx] standardmäßig keine Warnungen ausgegeben werden. Es werden jedoch einige Informationsmeldungen mit Erklärungen zum Code ausgegeben. Sie können optional auch weitere Informationsmeldungen aktivieren. Sie können z. B. eine optionale Informationswarnung (/warnon:3566) aktivieren, damit Verwendungen der expr.[idx]-Notation gemeldet werden. Weitere Informationen finden Sie in diesem Artikel zur Indexernotation.

In neuem Code empfehlen wir die systematische Verwendung von expr[idx] als Indizierungssyntax.

Dieses Feature implementiert F# RFC FS-1110.

Strukturdarstellungen für teilweise aktive Muster

F# 6 erweitert das Feature „aktive Muster“ um optionale Strukturdarstellungen für teilweise aktive Muster. Auf diese Weise können Sie ein Attribut verwenden, um ein teilweise aktives Muster auf die Rückgabe einer Wertoption zu beschränken:

[<return: Struct>]
let (|Int|_|) str =
   match System.Int32.TryParse(str) with
   | true, int -> ValueSome(int)
   | _ -> ValueNone

Die Verwendung des Attributs ist erforderlich. In Nutzungssites ändert sich der Code nicht. Unterm Strich bedeutet das, dass Speicherbelegungen reduziert werden.

Dieses Feature implementiert F# RFC FS-1039.

Überladene benutzerdefinierte Vorgänge in Berechnungsausdrücken

F# 6 ermöglicht die Verwendung von CustomOperationAttribute für die überladenen Methoden.

Sehen Sie sich die folgende Verwendung des Berechnungsausdruck-Generators content an:

let mem = new System.IO.MemoryStream("Stream"B)
let content = ContentBuilder()
let ceResult =
    content {
        body "Name"
        body (ArraySegment<_>("Email"B, 0, 5))
        body "Password"B 2 4
        body "BYTES"B
        body mem
        body "Description" "of" "content"
    }

Hier akzeptiert der benutzerdefinierten Vorgang body eine variierende Anzahl von Argumenten verschiedener Typen. Dies wird durch die Implementierung des folgenden Generators unterstützt, der Überladungen verwendet:

type Content = ArraySegment<byte> list

type ContentBuilder() =
    member _.Run(c: Content) =
        let crlf = "\r\n"B
        [|for part in List.rev c do
            yield! part.Array[part.Offset..(part.Count+part.Offset-1)]
            yield! crlf |]

    member _.Yield(_) = []

    [<CustomOperation("body")>]
    member _.Body(c: Content, segment: ArraySegment<byte>) =
        segment::c

    [<CustomOperation("body")>]
    member _.Body(c: Content, bytes: byte[]) =
        ArraySegment<byte>(bytes, 0, bytes.Length)::c

    [<CustomOperation("body")>]
    member _.Body(c: Content, bytes: byte[], offset, count) =
        ArraySegment<byte>(bytes, offset, count)::c

    [<CustomOperation("body")>]
    member _.Body(c: Content, content: System.IO.Stream) =
        let mem = new System.IO.MemoryStream()
        content.CopyTo(mem)
        let bytes = mem.ToArray()
        ArraySegment<byte>(bytes, 0, bytes.Length)::c

    [<CustomOperation("body")>]
    member _.Body(c: Content, [<ParamArray>] contents: string[]) =
        List.rev [for c in contents -> let b = Text.Encoding.ASCII.GetBytes c in ArraySegment<_>(b,0,b.Length)] @ c

Dieses Feature implementiert F# RFC FS-1056.

„as“-Muster

In F# 6 kann die rechte Seite eines as-Musters jetzt selbst ein Muster sein. Das ist wichtig, wenn ein Typtest einer Eingabe einen stärkeren Typ zugewiesen hat. Beachten Sie z. B. folgenden Code:

type Pair = Pair of int * int

let analyzeObject (input: obj) =
    match input with
    | :? (int * int) as (x, y) -> printfn $"A tuple: {x}, {y}"
    | :? Pair as Pair (x, y) -> printfn $"A DU: {x}, {y}"
    | _ -> printfn "Nope"

let input = box (1, 2)

Bei jedem Muster wird das Eingabeobjekt typgeprüft. Die rechte Seite des as-Musters darf jetzt wiederum ein Muster sein, das selbst mit dem Objekt des stärkeren Typs übereinstimmen kann.

Dieses Feature implementiert F# RFC FS-1105.

Überarbeitungen der Einzugssyntax

In F# 6 wurden einige Inkonsistenzen und Einschränkungen bei der Verwendung einer Syntax mit Einzügen entfernt. Siehe RFC FS-1108. Damit werden zehn wesentliche Probleme behoben, die von F#-Benutzern seit F# 4.0 gemeldet werden.

In F# 5 war beispielsweise der folgende Code zulässig:

let c = (
    printfn "aaaa"
    printfn "bbbb"
)

Der folgende Code war jedoch nicht zulässig (es wurde eine Warnung erzeugt):

let c = [
    1
    2
]

In F# 6 sind beide zulässig. Damit lässt sich F# einfacher erlernen und verwenden. Hadrian Tang, Mitglied der F#-Community, war hierbei federführend und hat das Feature höchst wertvollen systematischen Tests unterzogen.

Dieses Feature implementiert F# RFC FS-1108.

Weitere implizite Konvertierungen

In F# 6 wurde die Unterstützung für zusätzliche „implizite“ und „typgesteuerte“ Konvertierungen aktiviert, wie in RFC FS-1093 beschrieben.

Diese Änderung bringt drei Vorteile mit sich:

  1. Es sind weniger explizite Upcasts erforderlich.
  2. Es sind weniger explizite Integerkonvertierungen erforderlich.
  3. Es wurde erstklassige Unterstützung für implizite Konvertierungen im .NET-Stil hinzugefügt.

Dieses Feature implementiert F# RFC FS-1093.

Zusätzliche implizite Upcastkonvertierungen

F# 6 implementiert zusätzliche implizite Upcastkonvertierungen. In F# 5 und früheren Versionen wurden beispielsweise Upcasts für den Rückgabeausdruck benötigt, wenn eine Funktion implementiert wurde, bei der die Ausdrücke unterschiedliche Untertypen in verschiedenen Verzweigungen aufwiesen, auch wenn eine Typanmerkung vorhanden war. Sehen Sie sich den folgenden F# 5-Code an:

open System
open System.IO

let findInputSource () : TextReader =
    if DateTime.Now.DayOfWeek = DayOfWeek.Monday then
        // On Monday a TextReader
        Console.In
    else
        // On other days a StreamReader
        File.OpenText("path.txt") :> TextReader

Hier berechnen die Verzweigungen der Bedingung einen TextReader bzw. einen StreamReader, und der Upcast wird hinzugefügt, damit beide Verzweigungen den Typ „StreamReader“ aufweisen. In F# 6 werden diese Upcasts jetzt automatisch hinzugefügt. Damit wird der Code einfacher:

let findInputSource () : TextReader =
    if DateTime.Now.DayOfWeek = DayOfWeek.Monday then
        // On Monday a TextReader
        Console.In
    else
        // On other days a StreamReader
        File.OpenText("path.txt")

Optional können Sie die Warnung /warnon:3388 aktivieren, um an jedem Punkt, an dem ein zusätzlicher impliziter Upcast verwendet wird, eine Warnung anzuzeigen, wie in Optionale Warnungen für implizite Konvertierungen beschrieben.

Implizite Integerkonvertierungen

In F# 6 werden 32-Bit-Integer-Werte auf 64-Bit-Integer-Werte erweitert, wenn beide Typen bekannt sind. Sehen Sie sich beispielsweise eine typische API-Form an:

type Tensor(…) =
    static member Create(sizes: seq<int64>) = Tensor(…)

In F# 5 müssen Integerliterale für int64 verwendet werden:

Tensor.Create([100L; 10L; 10L])

oder

Tensor.Create([int64 100; int64 10; int64 10])

In F# 6 erfolgt die Erweiterung automatisch von int32 auf int64, von int32 auf nativeint und von int32 auf double, wenn während des Typrückschlusses sowohl Quell- als auch Zieltyp bekannt sind. In Fällen wie den obigen Beispielen können also int32-Literale verwendet werden:

Tensor.Create([100; 10; 10])

Trotz dieser Änderung verwendet F# in den meisten Fällen weiterhin eine explizite Erweiterung numerischer Typen. Die implizite Erweiterung gilt beispielsweise nicht für andere numerische Typen wie int8 oder int16 oder zur Erweiterung von float32 auf float64. Sie gilt auch dann nicht, wenn entweder der Quell- oder der Zieltyp unbekannt ist. Sie können optional auch die Warnung /warnon:3389 aktivieren, um an jedem Punkt, an dem eine zusätzliche implizite numerische Erweiterung verwendet wird, eine Warnung anzuzeigen, wie in Optionale Warnungen für implizite Konvertierungen beschrieben.

Erstklassige Unterstützung für implizite Konvertierungen im .NET-Stil

In F# 6 werden „op_Implicit“-Konvertierungen von .NET beim Aufrufen von Methoden automatisch im F#-Code angewendet. In F# 5 war bei der Arbeit mit .NET-APIs für XML beispielsweise die Verwendung von XName.op_Implicit erforderlich:

open System.Xml.Linq
let purchaseOrder = XElement.Load("PurchaseOrder.xml")
let partNos = purchaseOrder.Descendants(XName.op_Implicit "Item")

In F# 6 werden op_Implicit-Konvertierungen automatisch auf Argumentausdrücke angewendet, wenn die Typen für Quellausdruck und Zieltyp verfügbar sind:

open System.Xml.Linq
let purchaseOrder = XElement.Load("PurchaseOrder.xml")
let partNos = purchaseOrder.Descendants("Item")

Optional können Sie die Warnung /warnon:3395 aktivieren, um an jedem Punkt, an dem eine Erweiterung von op_Implicit-Konvertierungen in Methodenargumenten verwendet wird, eine Warnung anzuzeigen, wie in Optionale Warnungen für implizite Konvertierungen beschrieben.

Hinweis

In der ersten Version von F# 6 lautete diese Warnnummer /warnon:3390. Aufgrund eines Konflikts wurde die Warnnummer später zu /warnon:3395 aktualisiert.

Optionale Warnungen für implizite Konvertierungen

Typgesteuerte und implizite Konvertierungen interagieren zuweilen nur unzureichend mit Typrückschlüssen, was zu schwer verständlichem Code führen kann. Aus diesem Grund gibt es einige Entschärfungen, um sicherzustellen, dass dieses Feature im F#-Code nicht missbraucht wird. Zunächst muss sowohl der Quell- als auch der Zieltyp eindeutig bekannt sein, ohne jede Mehrdeutigkeit oder weitere Typrückschlüsse. Zweitens können Opt-In-Warnungen eingerichtet werden, die jede Verwendung impliziter Konvertierungen melden, wobei eine Warnung standardmäßig aktiviert ist:

  • /warnon:3388 (zusätzlicher impliziter Upcast)
  • /warnon:3389 (implizite numerische Erweiterung)
  • /warnon:3391 („op_Implicit“ bei Nicht-Methoden-Argumenten, standardmäßig aktiviert)
  • /warnon:3395 („op_Implicit“ bei Methodenargumenten)

Wenn Ihr Team jegliche Verwendung impliziter Konvertierungen untersagen möchte, können Sie auch /warnaserror:3388, /warnaserror:3389, /warnaserror:3391 und /warnaserror:3395 angeben.

Formatierung für Binärzahlen

F# 6 fügt den verfügbaren Formatbezeichnern das %B-Muster für binäre Zahlenformate hinzu. Sehen Sie sich den folgenden F#-Code an:

printf "%o" 123
printf "%B" 123

Dieser Code führt zu folgender Ausgabe:

173
1111011

Dieses Feature implementiert F# RFC FS-1100.

Verwerfen bei use-Bindungen

F# 6 lässt die Verwendung von _ in einer use-Bindung zu, z. B.:

let doSomething () =
    use _ = System.IO.File.OpenText("input.txt")
    printfn "reading the file"

Dieses Feature implementiert F# RFC FS-1102.

InlineIfLambda

Der F#-Compiler enthält einen Optimierer, der ein Inlining des Codes ausführt. In F# 6 haben wir ein neues deklaratives Feature hinzugefügt, mit dem der Code optional angeben kann, dass, wenn ein Argument als Lambdafunktion festgelegt wird, dieses Argument in Aufrufsites immer inline sein soll.

Betrachten Sie beispielsweise die folgende iterateTwice-Funktion zum Durchlaufen eines Arrays:

let inline iterateTwice ([<InlineIfLambda>] action) (array: 'T[]) =
    for j = 0 to array.Length-1 do
        action array[j]
    for j = 0 to array.Length-1 do
        action array[j]

Wenn die Aufrufseite folgendermaßen lautet:

let arr = [| 1.. 100 |]
let mutable sum = 0
arr  |> iterateTwice (fun x ->
    sum <- sum + x)

Dann sieht der Code nach Inlining und anderen Optimierungen so aus:

let arr = [| 1.. 100 |]
let mutable sum = 0
for j = 0 to arr.Length-1 do
    sum <- sum + arr[j]
for j = 0 to arr.Length-1 do
    sum <- sum + arr[j]

Im Gegensatz zu früheren Versionen von F# wird diese Optimierung unabhängig von der Größe des beteiligten Lambdaausdrucks angewendet. Dieses Feature kann auch verwendet werden, um die Auflösung von Schleifen und ähnliche Transformationen zuverlässiger zu implementieren.

Eine Opt-In-Warnung (/warnon:3517, standardmäßig deaktiviert) kann aktiviert werden, um Stellen in Ihrem Code anzugeben, an denen InlineIfLambda-Argumente nicht an Lambdaausdrücke in Aufrufsites gebunden sind. In normalen Situationen sollte diese Warnung nicht aktiviert werden. Bei bestimmten Arten der Hochleistungsprogrammierung kann sie jedoch hilfreich sein, sicherzustellen, dass der gesamte Code inline und vereinfacht ist.

Dieses Feature implementiert F# RFC FS-1098.

Fortsetzbarer Code

Die task {…}-Unterstützung von F# 6 basiert auf fortsetzbarem Code (resumable code) (RFC FS-1087). Fortsetzbarer Code ist ein technisches Feature, mit dem viele Arten von hochleistungsfähigen Zustandsautomaten („async“ und „yield“) erstellt werden können.

Weitere Sammlungsfunktionen

FSharp.Core 6.0.0 fügt den grundlegenden Sammlungsfunktionen fünf neue Vorgänge hinzu. Dabei handelt es sich um diese Funktionen:

  • List/Array/Seq.insertAt
  • List/Array/Seq.removeAt
  • List/Array/Seq.updateAt
  • List/Array/Seq.insertManyAt
  • List/Array/Seq.removeManyAt

Diese Funktionen führen alle Kopier- und Aktualisierungsvorgänge für den entsprechenden Sammlungstyp oder die entsprechende Sammlungssequenz aus. Dieser Vorgangstyp ist eine Art „Funktionsaktualisierung“. Beispiele für die Verwendung dieser Funktionen finden Sie in der entsprechenden Dokumentation, z. B. List.insertAt.

Betrachten Sie beispielsweise die Modell-, Nachrichten- und Aktualisierungslogik für eine einfache „Todo List“-Anwendung, die im Elmish-Stil geschrieben wurde. Hier interagiert der Benutzer mit der Anwendung und generiert dabei Nachrichten. Die update-Funktion verarbeitet diese Nachrichten und erzeugt ein neues Modell:

type Model =
    { ToDo: string list }

type Message =
    | InsertToDo of index: int * what: string
    | RemoveToDo of index: int
    | LoadedToDos of index: int * what: string list

let update (model: Model) (message: Message) =
    match message with
    | InsertToDo (index, what) ->
        { model with ToDo = model.ToDo |> List.insertAt index what }
    | RemoveToDo index ->
        { model with ToDo = model.ToDo |> List.removeAt index }
    | LoadedToDos (index, what) ->
        { model with ToDo = model.ToDo |> List.insertManyAt index what }

Mit diesen neuen Funktionen ist die Logik klar und einfach und basiert nur auf unveränderlichen Daten.

Dieses Feature implementiert F# RFC FS-1113.

„Map“ unterstützt „Keys“ und „Values“

In FSharp.Core 6.0.0 unterstützt der Map-Typ jetzt die Eigenschaften Keys und Values. Diese Eigenschaften kopieren die zugrunde liegende Sammlung nicht.

Dieses Feature ist in F# RFC FS-1113 dokumentiert.

Zusätzliche intrinsische Funktionen für NativePtr

FSharp.Core 6.0.0 fügt dem NativePtr-Modul neue intrinsische Funktionen hinzu:

  • NativePtr.nullPtr
  • NativePtr.isNullPtr
  • NativePtr.initBlock
  • NativePtr.clear
  • NativePtr.copy
  • NativePtr.copyBlock
  • NativePtr.ofILSigPtr
  • NativePtr.toILSigPtr

Wie anderen Funktionen in NativePtr auch sind diese Funktionen inline, und ihre Verwendung gibt Warnungen aus, sofern nicht /nowarn:9 verwendet wird. Die Verwendung dieser Funktionen ist auf nicht verwaltete Typen beschränkt.

Dieses Feature ist in F# RFC FS-1109 dokumentiert.

Zusätzliche numerische Typen mit Anmerkungen zu Einheiten

In F# 6 unterstützen die folgenden Typen oder Typkürzelaliase jetzt Anmerkungen zu Maßeinheiten. Die neuen Ergänzungen werden fett formatiert angezeigt:

F#-Alias CLR-Typ
float32/single System.Single
float/double System.Double
decimal System.Decimal
sbyte/int8 System.SByte
int16 System.Int16
int/int32 System.Int32
int64 System.Int64
byte/uint8 System.Byte
uint16 System.UInt16
uint/uint32 System.UInt32
uint64 System.UIn64
nativeint System.IntPtr
unativeint System.UIntPtr

Sie können z. B. eine ganze Zahl ohne Vorzeichen wie folgt mit einer Anmerkung versehen:

[<Measure>]
type days

let better_age = 3u<days>

Dieses Feature ist in F# RFC FS-1091 dokumentiert.

Informationswarnungen für selten verwendete Symboloperatoren

F# 6 fügt Empfehlungen hinzu, um die Verwendung von :=, !, incr und decr in F# 6 und darüber hinaus nach und nach zu entfernen. Die Verwendung dieser Operatoren und Funktionen erzeugt Informationsmeldungen, in denen Sie aufgefordert werden, Ihren Code durch die explizite Verwendung der Value-Eigenschaft zu ersetzen.

In der F#-Programmierung können Verweiszellen für dem Heap zugeordnete änderbare Register verwendet werden. Diese sind zwar gelegentlich nützlich, aber in der modernen F#-Programmierung nur selten erforderlich, da stattdessen let mutable verwendet werden kann. Die F#-Kernbibliothek enthält die beiden Operatoren := und ! sowie die beiden Funktionen incr und decr speziell im Zusammenhang mit Verweiszellen. Aufgrund dieser Operatoren sind Verweiszellen in der F#-Programmierung von zentralerer Bedeutung, als sie sein müssten. Daher müssen alle F#-Programmierer diese Operatoren kennen. Darüber hinaus kann der !-Operator leicht mit dem not-Vorgang in C# und anderen Sprachen verwechselt werden – eine Quelle für Fehler, die beim Übersetzen von Code nur schwer erkennbar sein können.

Ziel dieser Änderung ist es, die Anzahl der Operatoren zu reduzieren, die F#-Programmierer kennen müssen, und somit F# für Einsteiger zu vereinfachen.

Sehen Sie sich als Beispiel den folgenden F# 5-Code an:

let r = ref 0

let doSomething() =
    printfn "doing something"
    r := !r + 1

Zunächst werden Verweiszellen in der modernen F#-Programmierung selten benötigt, da normalerweise let mutable verwendet werden kann:

let mutable r = 0

let doSomething() =
    printfn "doing something"
    r <- r + 1

Wenn Sie Verweiszellen verwenden, gibt F# 6 eine Informationswarnung aus, in der Sie aufgefordert werden, die letzte Zeile zu r.Value <- r.Value + 1 zu ändern. Außerdem wird ein Link zu weiteren Informationen zur geeigneten Verwendung von Verweiszellen angezeigt.

let r = ref 0

let doSomething() =
    printfn "doing something"
    r.Value <- r.Value + 1

Diese Meldungen sind keine Warnungen, sondern Informationsmeldungen, die in der IDE- und Compilerausgabe angezeigt werden. F# ist weiterhin abwärtskompatibel.

Dieses Feature implementiert F# RFC FS-1111.

F#-Tools: .NET 6 als Standard für die Skripterstellung in Visual Studio

Wenn Sie ein F#-Skript (.fsx) in Visual Studio öffnen oder ausführen, wird es standardmäßig unter Verwendung von .NET 6 mit 64-Bit-Ausführung analysiert und ausgeführt. Diese Funktionalität befand sich in den späteren Versionen von Visual Studio 2019 in der Vorschau und ist jetzt standardmäßig aktiviert.

Zum Aktivieren der .NET Framework-Skripterstellung wählen Sie Tools>Optionen>F#-Tools>F# Interactive aus. Legen Sie .NET Core-Skripterstellung auf false fest, und starten Sie das F# Interactive-Fenster neu. Diese Einstellung wirkt sich sowohl auf die Bearbeitung als auch auf die Ausführung von Skripts aus. Um die 32-Bit-Ausführung für die .NET Framework-Skripterstellung zu aktivieren, legen Sie auch 64-Bit F# Interactive auf false fest. Es gibt keine 32-Bit-Option für die .NET Core-Skripterstellung.

F#-Tools: Anheften der SDK-Version Ihrer F#-Skripts

Wenn Sie ein Skript mit dotnet fsi in einem Verzeichnis ausführen, das eine global.json-Datei mit einer .NET SDK-Einstellung enthält, wird die aufgelistete Version des .NET SDK verwendet, um das Skript auszuführen und zu bearbeiten. Dieses Feature war in den späteren Versionen von F# 5 verfügbar.

Ein Beispiel: Es gibt ein Skript in einem Verzeichnis mit der folgenden global.json-Datei, das eine .NET SDK-Versionsrichtlinie angibt:

{
  "sdk": {
    "version": "5.0.200",
    "rollForward": "minor"
  }
}

Wenn Sie das Skript mit dotnet fsi in diesem Verzeichnis ausführen, wird die SDK-Version berücksichtigt. Dies ist ein leistungsstarkes Feature, mit dem Sie das SDK, das zum Kompilieren, Analysieren und Ausführen Ihrer Skripts verwendet wird, „sperren“ können.

Wenn Sie Ihr Skript in Visual Studio und anderen IDEs öffnen und bearbeiten, berücksichtigen die Tools diese Einstellung beim Analysieren und Überprüfen Ihres Skripts. Wenn das SDK nicht gefunden wird, müssen Sie es auf Ihrem Entwicklungscomputer installieren.

Unter Linux und anderen Unix-Systemen können Sie dies mit einem Shebang kombinieren, um auch eine Sprachversion für die direkte Ausführung des Skripts anzugeben. Ein einfacher Shebang für script.fsx ist:

#!/usr/bin/env -S dotnet fsi

printfn "Hello, world"

Jetzt kann das Skript direkt mit script.fsx ausgeführt werden. Sie können dies mit einer bestimmten, nicht standardmäßigen Sprachversion wie folgt kombinieren:

#!/usr/bin/env -S dotnet fsi --langversion:5.0

Hinweis

Diese Einstellung wird von Bearbeitungstools ignoriert, die das Skript unter Annahme der neuesten Sprachversion analysieren.

Entfernen von Legacyfeatures

Bereits seit F# 2.0 geben einige als veraltet markierte Legacyfeatures Warnungen aus. Bei Verwendung dieser Features in F# 6 werden Fehler ausgegeben, wenn Sie nicht explizit /langversion:5.0 verwenden. Folgende Features geben Fehler aus:

  • Mehrere generische Parameter mit einem Postfixtypnamen, z. B. (int, int) Dictionary. Dies wird in F# 6 zu einem Fehler. Stattdessen sollte die Standardsyntax Dictionary<int,int> verwendet werden.
  • #indent "off". Dies wird zu einem Fehler.
  • x.(expr). Dies wird zu einem Fehler.
  • module M = struct … end . Dies wird zu einem Fehler.
  • Verwenden der Eingaben *.ml und *.mli. Dies wird zu einem Fehler.
  • Verwenden von (*IF-CAML*) oder (*IF-OCAML*). Dies wird zu einem Fehler.
  • Verwenden von land, lor, lxor, lsl, lsr oder asr als Infixoperatoren. Dies sind Infixschlüsselwörter in F#, da sie in OCaml Infixschlüsselwörter waren und in FSharp.Core nicht definiert sind. Bei Verwendung dieser wird jetzt eine Warnung ausgegeben.

Dies implementiert F# RFC FS-1114.