Dela via


Nyheter i F# 6

F# 6 lägger till flera förbättringar av F#-språket och F# Interactive. Den släpps med .NET 6.

Du kan ladda ned den senaste .NET SDK:en från nedladdningssidan för .NET.

Kom igång

F# 6 finns i alla .NET Core-distributioner och Visual Studio-verktyg. Mer information finns i Komma igång med F#.

uppgift {...}

F# 6 innehåller inbyggt stöd för redigering av .NET-uppgifter i F#-kod. Tänk dig till exempel följande F#-kod för att skapa en . NET-kompatibel uppgift:

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

Med F# 6 kan den här koden skrivas om på följande sätt.

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

Uppgiftsstöd var tillgängligt för F# 5 via de utmärkta TaskBuilder.fs- och Ply-biblioteken. Det bör vara enkelt att migrera kod till det inbyggda stödet. Det finns dock vissa skillnader: namnrymder och typinferens skiljer sig något mellan det inbyggda stödet och dessa bibliotek, och vissa ytterligare typanteckningar kan behövas. Om det behövs kan du fortfarande använda dessa communitybibliotek med F# 6 om du refererar till dem explicit och öppnar rätt namnområden i varje fil.

Att använda task {…} är mycket likt att använda async {…}. Att använda har flera fördelar jämfört async {…}med task {…} :

  • Omkostnaderna task {...} för är lägre, vilket kan förbättra prestanda i heta kodsökvägar där det asynkrona arbetet körs snabbt.
  • Det är bättre att felsöka steg- och stackspårningar.task {…}
  • Det är enklare att samverka med .NET-paket som förväntar sig eller genererar uppgifter.

Om du är bekant med async {…}finns det några skillnader att vara medveten om:

  • task {…} kör omedelbart uppgiften till den första inväntningspunkten.
  • task {…} sprider inte implicit en annulleringstoken.
  • task {…} utför inte implicita annulleringskontroller.
  • task {…} stöder inte asynkrona tailcalls. Det innebär att rekursiv return! .. användning kan leda till stackspill om det inte finns några mellanliggande asynkrona väntetider.

I allmänhet bör du överväga att använda task {…} över async {…} i ny kod om du samverkar med .NET-bibliotek som använder uppgifter, och om du inte förlitar dig på asynkrona kod tailcalls eller implicit annulleringstokenspridning. I befintlig kod bör du bara växla till task {…} när du har granskat koden för att säkerställa att du inte förlitar dig på de tidigare nämnda egenskaperna för async {…}.

Den här funktionen implementerar F# RFC FS-1097.

Enklare indexeringssyntax med expr[idx]

F# 6 tillåter syntaxen expr[idx] för indexering och segmentering av samlingar.

Upp till och med F# 5 har F# använts expr.[idx] som indexeringssyntax. Att tillåta användning av expr[idx] baseras på upprepad feedback från dem som lär sig F# eller ser F# för första gången som användningen av dot-notation indexering framstår som en onödig skillnad från standard branschpraxis.

Detta är inte en icke-bakåtkompatibel ändring eftersom inga varningar genereras som standard om användningen av expr.[idx]. Vissa informationsmeddelanden som tyder på kod förtydliganden genereras dock. Du kan också aktivera ytterligare informationsmeddelanden. Du kan till exempel aktivera en valfri informationsvarning (/warnon:3566) för att börja rapportera användning av notationen expr.[idx] . Mer information finns i Indexer Notation.

I ny kod rekommenderar vi systematisk användning av expr[idx] som indexeringssyntax.

Den här funktionen implementerar F# RFC FS-1110.

Struct-representationer för partiella aktiva mönster

F# 6 utökar funktionen "aktiva mönster" med valfria structrepresentationer för partiella aktiva mönster. På så sätt kan du använda ett attribut för att begränsa ett partiellt aktivt mönster för att returnera ett värdealternativ:

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

Du måste använda attributet. På användningsplatser ändras inte koden. Nettoresultatet är att allokeringarna minskas.

Den här funktionen implementerar F# RFC FS-1039.

Överlagrade anpassade åtgärder i beräkningsuttryck

Med F# 6 kan du använda CustomOperationAttribute på de överlagrade metoderna.

Överväg följande användning av en beräkningsuttrycksbyggare content:

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"
    }

Här tar den body anpassade åtgärden ett varierande antal argument av olika typer. Detta stöds av implementeringen av följande byggare, som använder överlagring:

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

Den här funktionen implementerar F# RFC FS-1056.

"som"-mönster

I F# 6 kan den högra sidan av ett as mönster nu i sig vara ett mönster. Detta är viktigt när ett typtest har gett en starkare typ till indata. Tänk till exempel på följande kod:

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)

I varje mönsterfall är indataobjektet typtestat. Den högra sidan av as mönstret tillåts nu vara ytterligare ett mönster, som i sig kan matcha objektet vid den starkare typen.

Den här funktionen implementerar F# RFC FS-1105.

Indragssyntaxrevisioner

F# 6 tar bort ett antal inkonsekvenser och begränsningar i dess användning av indragsmedveten syntax. Se RFC FS-1108. Detta löser 10 viktiga problem som markerats av F#-användare sedan F# 4.0.

I F# 5 tilläts till exempel följande kod:

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

Följande kod tilläts dock inte (den skapade en varning):

let c = [
    1
    2
]

I F# 6 tillåts båda. Detta gör F# enklare och enklare att lära sig. F#-communitydeltagaren Hadrian Tang har gått i spetsen för detta, inklusive anmärkningsvärda och mycket värdefulla systematiska tester av funktionen.

Den här funktionen implementerar F# RFC FS-1108.

Ytterligare implicita konverteringar

I F# 6 har vi aktiverat stöd för ytterligare "implicita" och "typriktade" konverteringar, enligt beskrivningen i RFC FS-1093.

Den här ändringen medför tre fördelar:

  1. Färre explicita uppsändningar krävs
  2. Färre explicita heltalskonverteringar krävs
  3. Förstklassigt stöd för . Implicita konverteringar i NET-format läggs till

Den här funktionen implementerar F# RFC FS-1093.

Ytterligare implicita upcast-konverteringar

F# 6 implementerar ytterligare implicita uppcast-konverteringar. I F# 5 och tidigare versioner behövdes till exempel uppsändningar för returuttrycket vid implementering av en funktion där uttrycken hade olika undertyper på olika grenar, även när en typanteckning fanns. Överväg följande F# 5-kod:

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

Här har grenarna för den villkorsstyrda beräkningen och TextReaderStreamReader uppsändningen lagts till för att båda grenarna ska ha typen StreamReader. I F# 6 läggs dessa uppsändningar nu till automatiskt. Det innebär att koden är enklare:

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")

Du kan också aktivera varningen /warnon:3388 för att visa en varning vid varje tidpunkt som en ytterligare implicit uppsändning används, enligt beskrivningen i Valfria varningar för implicita konverteringar.

Implicita heltalskonverteringar

I F# 6 utvidgas 32-bitars heltal till 64-bitars heltal när båda typerna är kända. Tänk dig till exempel en typisk API-form:

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

I F# 5 måste heltal för int64 användas:

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

eller

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

I F# 6 sker breddning automatiskt för int32 till int64, int32 till nativeintoch int32 till double, när både käll- och måltyp är kända under typinferens. Så i fall som tidigare exempel int32 kan literaler användas:

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

Trots den här ändringen fortsätter F# att använda explicit breddning av numeriska typer i de flesta fall. Implicit breddning gäller till exempel inte för andra numeriska typer, till exempel int8 eller int16, eller från float32 till float64, eller när antingen käll- eller måltypen är okänd. Du kan också aktivera varningen /warnon:3389 för att visa en varning vid varje tidpunkt som implicit numerisk breddning används, enligt beskrivningen i Valfria varningar för implicita konverteringar.

Förstklassigt stöd för . Implicita konverteringar i NET-format

I F# 6 tillämpas .NET-konverteringar av "op_Implicit" automatiskt i F#-kod vid anrop av metoder. I F# 5 var det till exempel nödvändigt att använda XName.op_Implicit när du arbetade med .NET-API:er för XML:

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

I F# 6 op_Implicit tillämpas konverteringar automatiskt för argumentuttryck när typer är tillgängliga för källuttryck och måltyp:

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

Du kan också aktivera varningen /warnon:3395 för att visa en varning vid varje tidpunkt op_Implicit konverteringar vid breddning används vid metodargument, enligt beskrivningen i Valfria varningar för implicita konverteringar.

Kommentar

I den första versionen av F# 6 var det här varningsnumret /warnon:3390. På grund av en konflikt uppdaterades varningsnumret senare till /warnon:3395.

Valfria varningar för implicita konverteringar

Typstyrda och implicita konverteringar kan interagera dåligt med typinferens och leda till kod som är svårare att förstå. Därför finns det vissa åtgärder för att säkerställa att den här funktionen inte missbrukas i F#-kod. För det första måste både käll- och måltypen vara starkt känd, utan tvetydighet eller ytterligare typinferens som uppstår. För det andra kan opt-in-varningar aktiveras för att rapportera all användning av implicita konverteringar, med en varning aktiverad som standard:

  • /warnon:3388 (ytterligare implicit uppsändning)
  • /warnon:3389 (implicit numerisk breddning)
  • /warnon:3391 (op_Implicit vid argument som inte är metod, på som standard)
  • /warnon:3395 (op_Implicit vid metodargument)

Om ditt team vill förbjuda all användning av implicita konverteringar kan du även ange /warnaserror:3388, /warnaserror:3389, /warnaserror:3391och /warnaserror:3395.

Formatering för binära tal

F# 6 lägger till %B mönstret i de tillgängliga formatspecificerarna för binära talformat. Överväg följande F#-kod:

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

Den här koden skriver ut följande utdata:

173
1111011

Den här funktionen implementerar F# RFC FS-1100.

Tar bort vid användningsbindningar

F# 6 kan _ användas i en use bindning, till exempel:

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

Den här funktionen implementerar F# RFC FS-1102.

InlineIfLambda

F#-kompilatorn innehåller en optimerare som utför inlinning av kod. I F# 6 har vi lagt till en ny deklarativ funktion som gör det möjligt för kod att ange att om ett argument är fast beslutet att vara en lambda-funktion bör det argumentet alltid anges på anropsplatser.

Tänk dig till exempel följande iterateTwice funktion för att korsa en matris:

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]

Om samtalswebbplatsen är:

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

Efter inlining och andra optimeringar blir koden:

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]

Till skillnad från tidigare versioner av F#tillämpas den här optimeringen oavsett storleken på lambda-uttrycket. Den här funktionen kan också användas för att implementera loop-avregistrering och liknande transformeringar på ett mer tillförlitligt sätt.

En opt-in-varning (/warnon:3517inaktiverad som standard) kan aktiveras för att ange platser i koden där InlineIfLambda argument inte är bundna till lambda-uttryck på anropsplatser. I normala situationer bör den här varningen inte aktiveras. I vissa typer av programmering med höga prestanda kan det dock vara användbart att se till att all kod är inlindad och utplattad.

Den här funktionen implementerar F# RFC FS-1098.

Återanvändbar kod

Stödet task {…} för F# 6 bygger på en grund som kallas återtagandekodRFC FS-1087. Återanvändbar kod är en teknisk funktion som kan användas för att skapa många typer av högpresterande asynkrona och ger tillståndsdatorer.

Ytterligare samlingsfunktioner

FSharp.Core 6.0.0 lägger till fem nya åtgärder i kärnsamlingsfunktionerna. Dessa funktioner är:

  • Lista/Matris/Seq.insertAt
  • Lista/Matris/Seq.removeAt
  • Lista/Matris/Seq.updateAt
  • Lista/Matris/Seq.insertManyAt
  • Lista/Matris/Seq.removeManyAt

Dessa funktioner utför alla kopierings- och uppdateringsåtgärder på motsvarande samlingstyp eller sekvens. Den här typen av åtgärd är en form av en "funktionell uppdatering". Exempel på hur du använder dessa funktioner finns i motsvarande dokumentation, till exempel List.insertAt.

Tänk till exempel på modellen, meddelandet och uppdateringslogik för ett enkelt "Todo List"-program som skrivits i Elmish-stil. Här interagerar användaren med programmet, genererar meddelanden och update funktionen bearbetar dessa meddelanden och skapar en ny 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 }

Med dessa nya funktioner är logiken tydlig och enkel och förlitar sig bara på oföränderliga data.

Den här funktionen implementerar F# RFC FS-1113.

Kartan har nycklar och värden

I FSharp.Core 6.0.0 Map stöder typen nu egenskaperna Nycklar och värden . Dessa egenskaper kopierar inte den underliggande samlingen.

Den här funktionen är dokumenterad i F# RFC FS-1113.

Ytterligare inbyggda egenskaper för NativePtr

FSharp.Core 6.0.0 lägger till nya inbyggda funktioner i NativePtr-modulen :

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

Precis som med andra funktioner i NativePträr dessa funktioner inlindade och deras användning genererar varningar om de inte /nowarn:9 används. Användningen av dessa funktioner är begränsad till ohanterade typer.

Den här funktionen finns dokumenterad i F# RFC FS-1109.

Ytterligare numeriska typer med enhetsanteckningar

I F# 6 stöder följande typer eller typförkortningsalias nu enhetsanteckningar. De nya tilläggen visas i fetstil:

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

Du kan till exempel kommentera ett osignerat heltal på följande sätt:

[<Measure>]
type days

let better_age = 3u<days>

Den här funktionen finns dokumenterad i F# RFC FS-1091.

Informationsvarningar för sällan använda symboliska operatorer

F# 6 lägger till mjuk vägledning som avnormaliserar användningen av :=, !, incroch decr i F# 6 och senare. Med hjälp av dessa operatorer och funktioner skapas informationsmeddelanden som ber dig att ersätta koden med explicit användning av Value egenskapen.

I F#-programmering kan referensceller användas för heapallokerade föränderliga register. Även om de ibland är användbara behövs de sällan i modern F#-kodning, eftersom let mutable de kan användas i stället. F#-kärnbiblioteket innehåller två operatorer := och ! två funktioner incr och decr specifikt relaterade till referensanrop. Förekomsten av dessa operatorer gör referenscellerna mer centrala i F#-programmeringen än de behöver vara, vilket kräver att alla F#-programmerare känner till dessa operatorer. Dessutom kan operatorn ! enkelt förväxlas med not åtgärden i C# och andra språk, en potentiellt subtil källa till buggar vid översättning av kod.

Syftet med den här ändringen är att minska antalet operatörer som F#-programmeraren behöver känna till och därmed förenkla F# för nybörjare.

Tänk till exempel på följande F# 5-kod:

let r = ref 0

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

För det första behövs referensceller sällan i modern F#-kodning, vilket let mutable normalt kan användas i stället:

let mutable r = 0

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

Om du använder referensceller genererar F# 6 en informationsvarning där du uppmanas att ändra den sista raden till r.Value <- r.Value + 1och länka dig till ytterligare vägledning om lämplig användning av referensceller.

let r = ref 0

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

Dessa meddelanden är inte varningar. de är "informationsmeddelanden" som visas i IDE- och kompilatorutdata. F# förblir bakåtkompatibel.

Den här funktionen implementerar F# RFC FS-1111.

F#-verktyg: .NET 6 som standard för skript i Visual Studio

Om du öppnar eller kör ett F#-skript (.fsx) i Visual Studio analyseras och körs skriptet som standard med .NET 6 med 64-bitars körning. Den här funktionen var i förhandsversion i senare versioner av Visual Studio 2019 och är nu aktiverad som standard.

Om du vill aktivera .NET Framework-skript väljer du Verktygsalternativ>>F# Verktyg>F# Interaktiv. Ange Använd .NET Core-skript till false och starta sedan om det interaktiva F#-fönstret. Den här inställningen påverkar både skriptredigering och skriptkörning. Om du vill aktivera 32-bitars körning för .NET Framework-skript anger du även 64-bitars F# Interactive till false. Det finns inget 32-bitarsalternativ för .NET Core-skript.

F#-verktyg: Fäst SDK-versionen av dina F#-skript

Om du kör ett skript med i dotnet fsi en katalog som innehåller en global.json fil med en .NET SDK-inställning används den angivna versionen av .NET SDK för att köra och redigera skriptet. Den här funktionen har varit tillgänglig i senare versioner av F# 5.

Anta till exempel att det finns ett skript i en katalog med följande global.json fil som anger en .NET SDK-versionsprincip:

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

Om du nu kör skriptet med hjälp av dotnet fsi, från den här katalogen, kommer SDK-versionen att respekteras. Det här är en kraftfull funktion som gör att du kan "låsa" SDK:et som används för att kompilera, analysera och köra dina skript.

Om du öppnar och redigerar skriptet i Visual Studio och andra IDE:er respekterar verktygen den här inställningen när du analyserar och kontrollerar skriptet. Om SDK:et inte hittas måste du installera det på utvecklingsdatorn.

I Linux och andra Unix-system kan du kombinera detta med en shebang för att även ange en språkversion för direkt körning av skriptet. En enkel shebang för script.fsx är:

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

printfn "Hello, world"

Nu kan skriptet köras direkt med script.fsx. Du kan kombinera detta med en specifik språkversion som inte är standard, så här:

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

Kommentar

Den här inställningen ignoreras av redigeringsverktyg, som analyserar skriptet med den senaste språkversionen.

Ta bort äldre funktioner

Sedan F# 2.0 har vissa inaktuella äldre funktioner länge gett varningar. Om du använder dessa funktioner i F# 6 får du fel om du inte uttryckligen använder /langversion:5.0. De funktioner som ger fel är:

  • Flera generiska parametrar med ett postfixtypnamn, till exempel (int, int) Dictionary. Detta blir ett fel i F# 6. Standardsyntaxen Dictionary<int,int> ska användas i stället.
  • #indent "off". Detta blir ett fel.
  • x.(expr). Detta blir ett fel.
  • module M = struct … end . Detta blir ett fel.
  • Användning av indata *.ml och *.mli. Detta blir ett fel.
  • Användning av (*IF-CAML*) eller (*IF-OCAML*). Detta blir ett fel.
  • Användning av landoperatorerna , lor, lxor, lsl, lsreller asr som infix. Det här är infixnyckelord i F# eftersom de var infixnyckelord i OCaml och inte definieras i FSharp.Core. Med hjälp av dessa nyckelord genereras nu en varning.

Detta implementerar F# RFC FS-1114.