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 rekursivreturn! ..
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:
- Färre explicita uppsändningar krävs
- Färre explicita heltalskonverteringar krävs
- 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 TextReader
StreamReader
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 nativeint
och 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:3391
och /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:3517
inaktiverad 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 :=
, !
, incr
och 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 + 1
och 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. StandardsyntaxenDictionary<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
land
operatorerna ,lor
,lxor
,lsl
,lsr
ellerasr
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.