Ankündigung der F # 4.6-Vorschau

Wir freuen uns, Ihnen mitteilen zu können, dass Visual Studio 2019 eine neue Version von F # liefert, wenn es veröffentlicht wird: F # 4.6!


F # 4.6 ist ein kleineres Update der F # -Sprache, was es zu einer „echten“ Punktveröffentlichung macht. Wie bei früheren Versionen von F # wurde F # 4.6 vollständig über einen offenen RFC-Prozess (Requests for Comments) entwickelt. Die F # Community hat in Diskussionen zu dieser Version der Sprache sehr detailliertes Feedback gegeben. Sie können alle RFCs, die dieser Version entsprechen, hier anzeigen:



In diesem Beitrag werden die Funktionen und die ersten Schritte beschrieben.


Original im Blog

Fangen Sie an


Installieren Sie zunächst entweder:



Aktualisieren Sie als Nächstes Ihre FSharp.Core-Abhängigkeit auf FSharp.Core 4.6 (oder höher). Wenn Sie Visual Studio verwenden, können Sie dies mit der NuGet Package Management-Benutzeroberfläche tun. Wenn Sie Visual Studio nicht verwenden oder Projektdateien von Hand bearbeiten möchten, fügen Sie dies der Projektdatei hinzu:


<ItemGroup> <PackageReference Update="FSharp.Core" Version="4.6.0" /> </ItemGroup> 

Sobald Sie die erforderlichen Bits installiert haben, können Sie F # 4.6 mit Visual Studio , Visual Studio für Mac oder Visual Studio Code mit Ionide verwenden .


Anonyme Aufzeichnungen


Abgesehen von verschiedenen Fehlerkorrekturen ist die einzige Sprachänderung in F # 4.6 die Einführung anonymer Datensatztypen .


Grundlegende Verwendung


Nur aus F # -Perspektive sind anonyme Datensätze F # -Datensatztypen, die keine expliziten Namen haben und in einer Ad-hoc-Fasion deklariert werden können. Obwohl es unwahrscheinlich ist, dass sie die Art und Weise, wie Sie F # -Code schreiben, grundlegend ändern, füllen sie viele kleinere Lücken, auf die F # -Programmierer im Laufe der Zeit gestoßen sind, und können für prägnante Datenmanipulationen verwendet werden, die zuvor nicht möglich waren.


Sie sind recht einfach zu bedienen. Hier erfahren Sie beispielsweise, wie Sie mit einer Funktion interagieren können, die einen anonymen Datensatz erstellt:


 open System let circleStats radius = let d = radius * 2.0 let a = Math.PI * (radius ** 2.0) let c = 2.0 * Math.PI * radius {| Diameter=d; Area=a; Circumference=c |} let r = 2.0 let stats = circleStats r printfn "Circle with radius: %f has diameter %f, area %f, and circumference %f" r stats.Diameter stats.Area stats.Circumference 

Sie können jedoch nicht nur für grundlegende Datencontainer verwendet werden. Im Folgenden wird das vorherige Beispiel um eine typsicherere Druckfunktion erweitert:


 let circleStats radius = let d = radius * 2.0 let a = Math.PI * (radius ** 2.0) let c = 2.0 * Math.PI * radius {| Diameter=d; Area=a; Circumference=c |} let printCircleStats r (stats: {| Area: float; Circumference: float; Diameter: float |}) = printfn "Circle with radius: %f has diameter %f, area %f, and circumference %f" r stats.Diameter stats.Area stats.Circumference let r = 2.0 let stats = circleStats r printCircleStats r stats 

Wenn Sie versuchen, "printCircleStats" mit einem anonymen Datensatz aufzurufen, der dieselben zugrunde liegenden Datentypen, aber unterschiedliche Bezeichnungen aufweist, kann Folgendes nicht kompiliert werden:


 printCircleStats r {| Diameter=2.0; Area=4.0; MyCircumference=12.566371 |} // Two anonymous record types have mismatched sets of field names '["Area"; "Circumference"; "Diameter"]' and '["Area"; "Diameter"; "MyCircumference"]' 

Genau so funktionieren F # -Datensatztypen, außer dass alles ad-hoc und nicht im Voraus deklariert wurde. Dies hat je nach Ihrer speziellen Situation Vor- und Nachteile. Wir empfehlen daher, anonyme Datensätze mit Bedacht zu verwenden, anstatt alle Ihre vorherigen F # -Datensatzdeklarationen zu ersetzen.


Strukturieren Sie anonyme Datensätze


Anonyme Datensätze können auch Strukturen sein, indem das Schlüsselwort struct verwendet wird :


 open System let circleStats radius = let d = radius * 2.0 let a = Math.PI * (radius ** 2.0) let c = 2.0 * Math.PI * radius // Note that the keyword comes before the '{| |}' brace pair struct {| Area=a; Circumference=c; Diameter=d |} // the 'struct' keyword also comes before the '{| |}' brace pair when declaring the parameter type let printCircleStats r (stats: struct {| Area: float; Circumference: float; Diameter: float |}) = printfn "Circle with radius: %f has diameter %f, area %f, and circumference %f" r stats.Diameter stats.Area stats.Circumference let r = 2.0 let stats = circleStats r printfn "Circle with radius: %f has diameter %f, area %f, and circumference %f" r stats.Diameter stats.Area stats.Circumference 

Sie können eine Funktion aufrufen, die einen anonymen Strukturdatensatz verwendet. Dies kann explizit folgendermaßen erfolgen:


 let printCircleStats r (stats: struct {| Area: float; Circumference: float; Diameter: float |}) = printfn "Circle with radius: %f has diameter %f, area %f, and circumference %f" r stats.Diameter stats.Area stats.Circumference printCircleStats r struct {| Area=4.0; Circumference=12.6; Diameter=12.6 |} 

Oder Sie können "Strukturinferenz" verwenden, um die "Struktur" an der Aufrufstelle zu entfernen:


 let printCircleStats r (stats: struct {| Area: float; Circumference: float; Diameter: float |}) = printfn "Circle with radius: %f has diameter %f, area %f, and circumference %f" r stats.Diameter stats.Area stats.Circumference printCircleStats r {| Area=4.0; Circumference=12.6; Diameter=12.6 |} 

Dadurch wird die Instanz des anonymen Datensatzes, den Sie erstellt haben, so behandelt, als wäre es eine Struktur.


Beachten Sie, dass das Gegenteil nicht der Fall ist:


 let printCircleStats r (stats: {| Area: float; Circumference: float; Diameter: float |}) = printfn "Circle with radius: %f has diameter %f, area %f, and circumference %f" r stats.Diameter stats.Area stats.Circumference // This will fail to compile for hopefully obvious reasons! printCircleStats r struct {| Area=4.0; Circumference=12.6; Diameter=12.6 |} 

Es ist derzeit nicht möglich, anonyme Datensatztypen für IsByRefLike oder IsReadOnly zu definieren. Es gibt einen Sprachvorschlag , der diese Verbesserung vorschlägt, der jedoch aufgrund von Kuriositäten in der Syntax noch diskutiert wird.


Die Dinge weiter bringen


Anonyme Datensätze können in einem breiteren Satz fortgeschrittener Kontexte verwendet werden.


Anonyme Datensätze sind serialisierbar


Sie können anonyme Datensätze serialisieren und deserialisieren:


 open Newtonsoft.Json let phillip = {| name="Phillip"; age=28 |} let str = JsonConvert.SerializeObject(phillip) printfn "%s" str let phillip' = JsonConvert.DeserializeObject<{|name: string; age: int|}>(str) printfn "Name: %s Age: %d" phillip'.name phillip'.age 

Dies gibt aus, was Sie erwarten könnten:


 {"age":28,"name":"Phillip"} Name: Phillip Age: 28 

Hier ist eine Beispielbibliothek, die auch in einem anderen Projekt aufgerufen wird:


 namespace AnonyRecdOne open Newtonsoft.Json module AR = let serialize () = let phillip = {| name="Phillip"; age=28 |} JsonConvert.SerializeObject(phillip) 

 open AnonyRecdOne open Newtonsoft.Json [<EntryPoint>] let main _ = let str = AR.serialize () let phillip = JsonConvert.DeserializeObject<{|name: string; age: int|}>(str) printfn "Name: %s Age: %d" phillip.name phillip.age 

Dies kann Szenarien wie Lightweight-Daten, die über ein Netzwerk in einem System aus Microservices übertragen werden, vereinfachen.


Anonyme Datensätze können mit anderen Typdefinitionen kombiniert werden


Möglicherweise befindet sich in Ihrer Domäne ein baumartiges Datenmodell, z. B. das folgende Beispiel:


 type FullName = { FirstName: string; LastName: string } type Employee = | Engineer of FullName | Manager of name: FullName * reports: Employee list | Executive of name: FullName * reports: Employee list * assistant: Employee 

Es ist typisch, Fälle als Tupel mit benannten Vereinigungsfeldern zu modellieren. Wenn die Daten jedoch komplizierter werden, können Sie jeden Fall mit Datensätzen extrahieren:


 type FullName = { FirstName: string; LastName: string } type Employee = | Engineer of FullName | Manager of Manager | Executive of Executive and Manager = { Name: FullName; Reports: Employee list } and Executive = { Name: FullName; Reports: Employee list; Assistant: Employee } 

Diese rekursive Definition kann jetzt mit anonymen Datensätzen gekürzt werden, wenn sie zu Ihrer Codebasis passt:


 type FullName = { FirstName: string; LastName: string } type Employee = | Engineer of FullName | Manager of {| Name: FullName; Reports: Employee list |} | Executive of {| Name: FullName; Reports: Employee list; Assistant: Employee |} 

Wie bei den vorherigen Beispielen sollte diese Technik mit Bedacht und gegebenenfalls auf Ihr Szenario angewendet werden.


Anonyme Datensätze vereinfachen die Verwendung von LINQ in F #


F # -Programmierer bevorzugen normalerweise die Verwendung der Kombinatoren List, Array und Sequence, wenn sie mit Daten arbeiten. Manchmal kann es jedoch hilfreich sein, LINQ zu verwenden. Dies war traditionell etwas schmerzhaft, da LINQ anonyme C # -Typen verwendet.


Bei anonymen Datensätzen können Sie LINQ-Methoden genauso verwenden wie bei C # und anonymen Typen:


 open System.Linq let names = [ "Ana"; "Felipe"; "Emillia"] let nameGrouping = names.Select(fun n -> {| Name=n; FirstLetter=n.[0] |}) for ng in nameGrouping do printfn "%s has first letter %c" ng.Name ng.FirstLetter 

Dies druckt:


 Ana has first letter A Felipe has first letter F Emillia has first letter E 

Anonyme Datensätze erleichtern die Arbeit mit Entity Framework und anderen ORMs


F # -Programmierer, die F # -Abfrageausdrücke verwenden, um mit einer Datenbank zu interagieren, sollten bei anonymen Datensätzen geringfügige Verbesserungen der Lebensqualität feststellen.


Beispielsweise können Sie es gewohnt sein, Tupel zu verwenden, um Daten mit einer `select` -Klausel zu gruppieren:


 let q = query { for row in db.Status do select (row.StatusID, row.Name) } 

Dies führt jedoch zu Spalten mit Namen wie Item1 und Item2 , die nicht ideal sind. Vor anonymen Datensätzen müssten Sie einen Datensatztyp deklarieren und diesen verwenden. Jetzt müssen Sie das nicht mehr tun:


 let q = query { for row in db.Status do select {| StatusID = row.StatusID; Name = row.Name |} } 

Der Datensatztyp muss nicht im Voraus angegeben werden! Dadurch werden Abfrageausdrücke viel stärker an der tatsächlichen SQL ausgerichtet, die sie modellieren.


Mit anonymen Datensätzen müssen Sie außerdem vermeiden, AnonymousObject- Typen in erweiterten Abfragen erstellen zu müssen, um eine Ad-hoc-Gruppierung von Daten für die Zwecke der Abfrage zu erstellen.


Anonyme Datensätze vereinfachen die Verwendung von benutzerdefiniertem Routing in ASP.NET Core


Möglicherweise verwenden Sie ASP.NET Core bereits mit F #, sind jedoch beim Definieren benutzerdefinierter Routen möglicherweise auf Schwierigkeiten gestoßen. Wie bei den vorherigen Beispielen könnte dies immer noch durch Definieren eines Datensatztyps im Voraus erfolgen, dies wurde jedoch von F # -Entwicklern häufig als unnötig angesehen. Jetzt können Sie es inline tun:


 app.UseMvc(fun routes -> routes.MapRoute("blog","blog/{*article}", defaults={| controller="Blog"; action="Article" |}) |> ignore ) |> ignore 

Es ist immer noch nicht ideal, da F # bei Rückgabetypen streng ist (im Gegensatz zu C #, wo Sie Dinge, die einen Wert zurückgeben, nicht explizit ignorieren müssen). Auf diese Weise können Sie jedoch zuvor definierte Datensatzdefinitionen entfernen, die nur dazu dienten, Daten an die ASP.NET-Middleware-Pipeline zu senden.


Kopieren und aktualisieren Sie Ausdrücke mit anonymen Datensätzen


Wie bei Datensatztypen können Sie die Syntax zum Kopieren und Aktualisieren mit anonymen Datensätzen verwenden:


 let data = {| X = 1; Y = 2 |} let expandedData = {| data with Z = 3 |} // Gives {| X=1; Y=2; Z=3 |} let stringifiedY = {| expandedData with Y="Hello!" |} // Gives {| X=1; Y="Hello!"; Z=3 |} 

Der ursprüngliche Ausdruck kann auch ein Datensatztyp sein:


 type R = { X: int } let data = { X=1 } let data' = {| data with Y = 2 |} // Gives {| X=1; Y=2 |} 

Sie können auch Daten in und aus Referenz- und Struktur-anonymen Datensätzen kopieren:


 // Copy data from a reference record into a struct anonymous record type R1 = { X: int } let r1 = { X=1 } let data1 = struct {| r1 with Y=1 |} // Copy data from a struct record into a reference anonymous record [<Struct>] type R2 = { X: int } let r2 = { X=1 } let data2 = {| r1 with Y=1 |} 

Die Verwendung von Kopier- und Aktualisierungsausdrücken bietet anonymen Datensätzen ein hohes Maß an Flexibilität bei der Arbeit mit Daten in F #.


Gleichheit und Musterübereinstimmung


Anonyme Datensätze sind strukturell gleichwertig und vergleichbar:


 {| a = 1+1 |} = {| a = 2 |} // true {| a = 1+1 |} > {| a = 1 |} // true 

Die verglichenen Typen müssen jedoch dieselbe „Form“ haben:


 // error FS0001: Two anonymous record types have mismatched sets of field names '["a"]' and '["a"; "b"]' {| a = 1+1 |} = {| a = 2; b = 1|} 

Obwohl Sie anonyme Datensätze gleichsetzen und vergleichen können, können Sie keine Musterübereinstimmung darüber durchführen. Dies hat zwei Gründe:


  • Im Gegensatz zu Datensatztypen muss ein Muster jedes Feld eines anonymen Datensatzes berücksichtigen. Dies liegt daran, dass anonyme Datensätze keine strukturelle Untertypisierung unterstützen - es handelt sich um nominelle Typen.
  • Es gibt keine Möglichkeit, zusätzliche Muster in einem Musterübereinstimmungsausdruck zu haben, da jedes einzelne Muster einen anderen anonymen Datensatztyp implizieren würde.
  • Die Anforderung, jedes Feld in einem anonymen Datensatz zu berücksichtigen, würde ein Muster ausführlicher machen als die Verwendung der Punktnotation.

Stattdessen wird "dot" -Syntax verwendet, um Werte aus einem anonymen Datensatz zu extrahieren. Dies ist immer höchstens so ausführlich, als ob ein Mustervergleich verwendet würde, und in der Praxis ist es wahrscheinlich weniger ausführlich, da nicht immer jeder Wert aus einem anonymen Datensatz extrahiert wird. So arbeiten Sie mit einem vorherigen Beispiel, in dem anonyme Datensätze Teil einer diskriminierten Vereinigung sind:


 type Employee = | Engineer of FullName | Manager of {| Name: FullName; Reports: Employee list |} | Executive of {| Name: FullName; Reports: Employee list; Assistant: Employee |} let getFirstName e = match e with | Engineer fullName -> fullName.FirstName | Manager m -> m.Name.FirstName | Executive ex -> ex.Name.FirstName 

Derzeit gibt es einen offenen Vorschlag , den Mustervergleich für anonyme Datensätze in den begrenzten Kontexten zuzulassen, in denen sie tatsächlich aktiviert werden könnten. Wenn Sie einen vorgeschlagenen Anwendungsfall haben, verwenden Sie dieses Problem, um ihn zu diskutieren!


FSharp.Core Ergänzungen


Es wäre keine weitere F # -Veröffentlichung ohne Ergänzungen der F # -Kernbibliothek!


ValueOption-Erweiterung


Der in F # 4.5 eingeführte ValueOption- Typ enthält jetzt einige weitere Extras:


  • Das DebuggerDisplay- Attribut zur Unterstützung beim Debuggen
  • IsNone- , IsSome- , None- , Some- , op_Implicit- und ToString- Mitglieder

Dies gibt ihm "Parität" mit dem Optionstyp.


Zusätzlich gibt es jetzt ein ValueOption- Modul, das dieselben Funktionen enthält wie das Option-Modul:


 module ValueOption = [<CompiledName("IsSome")>] val inline isSome: voption:'T voption -> bool [<CompiledName("IsNone")>] val inline isNone: voption:'T voption -> bool [<CompiledName("DefaultValue")>] val defaultValue: value:'T -> voption:'T voption -> 'T [<CompiledName("DefaultWith")>] val defaultWith: defThunk:(unit -> 'T) -> voption:'T voption -> 'T [<CompiledName("OrElse")>] val orElse: ifNone:'T voption -> voption:'T voption -> 'T voption [<CompiledName("OrElseWith")>] val orElseWith: ifNoneThunk:(unit -> 'T voption) -> voption:'T voption -> 'T voption [<CompiledName("GetValue")>] val get: voption:'T voption -> 'T [<CompiledName("Count")>] val count: voption:'T voption -> int [<CompiledName("Fold")>] val fold<'T,'State> : folder:('State -> 'T -> 'State) -> state:'State -> voption:'T voption -> 'State [<CompiledName("FoldBack")>] val foldBack<'T,'State> : folder:('T -> 'State -> 'State) -> voption:'T voption -> state:'State -> 'State [<CompiledName("Exists")>] val exists: predicate:('T -> bool) -> voption:'T voption -> bool [<CompiledName("ForAll")>] val forall: predicate:('T -> bool) -> voption:'T voption -> bool [<CompiledName("Contains")>] val inline contains: value:'T -> voption:'T voption -> bool when 'T : equality [<CompiledName("Iterate")>] val iter: action:('T -> unit) -> voption:'T voption -> unit [<CompiledName("Map")>] val map: mapping:('T -> 'U) -> voption:'T voption -> 'U voption [<CompiledName("Map2")>] val map2: mapping:('T1 -> 'T2 -> 'U) -> voption1: 'T1 voption -> voption2: 'T2 voption -> 'U voption [<CompiledName("Map3")>] val map3: mapping:('T1 -> 'T2 -> 'T3 -> 'U) -> 'T1 voption -> 'T2 voption -> 'T3 voption -> 'U voption [<CompiledName("Bind")>] val bind: binder:('T -> 'U voption) -> voption:'T voption -> 'U voption [<CompiledName("Flatten")>] val flatten: voption:'T voption voption -> 'T voption [<CompiledName("Filter")>] val filter: predicate:('T -> bool) -> voption:'T voption -> 'T voption [<CompiledName("ToArray")>] val toArray: voption:'T voption -> 'T[] [<CompiledName("ToList")>] val toList: voption:'T voption -> 'T list [<CompiledName("ToNullable")>] val toNullable: voption:'T voption -> Nullable<'T> [<CompiledName("OfNullable")>] val ofNullable: value:Nullable<'T> -> 'T voption [<CompiledName("OfObj")>] val ofObj: value: 'T -> 'T voption when 'T : null [<CompiledName("ToObj")>] val toObj: value: 'T voption -> 'T when 'T : null 

Dies sollte alle Bedenken zerstreuen, dass "ValueOption" das seltsame Geschwister von "Option" ist, das nicht die gleiche Funktionalität erhält.


tryExactlyOne für List, Array und Seq


Diese feine Funktion wurde von Grzegorz Dziadkiewicz beigesteuert . So funktioniert es:


 List.tryExactlyOne [] // None List.tryExactlyOne [1] // Some 1 List.tryExactlyOne [1; 2] // None Array.tryExactlyOne null // ArgumentNullException Array.tryExactlyOne [||] // None Array.tryExactlyOne [|1|] // Some 1 Array.tryExactlyOne [|1; 2|] // None Seq.tryExactlyOne null // ArgumentNullException Seq.tryExactlyOne (Seq.ofList []) // None Seq.tryExactlyOne (Seq.ofList [1]) // Some 1 Seq.tryExactlyOne (Seq.ofList [1; 2]) // None 

Einpacken


Obwohl die Gesamtliste der Funktionen in F # 4.6 nicht riesig ist, gehen sie dennoch ziemlich tief! Wir empfehlen Ihnen, F # 4.6 auszuprobieren und uns Feedback zu geben, damit wir die Dinge vor der vollständigen Veröffentlichung optimieren können. Wie immer danke ich der F # Community für ihre Beiträge - sowohl in der Code- als auch in der Designdiskussion - die uns helfen, die F # -Sprache weiter voranzutreiben.


Prost und viel Spaß beim Hacken!

Source: https://habr.com/ru/post/de437666/


All Articles