Anonymní záznamy
Anonymní záznamy jsou jednoduché agregace pojmenovaných hodnot, které před použitím nemusí být deklarovány. Můžete je deklarovat jako struktury nebo odkazové typy. Ve výchozím nastavení se jedná o odkazové typy.
Syntaxe
Následující příklady ukazují syntaxi anonymního záznamu. Položky s oddělovači jsou [item]
volitelné.
// Construct an anonymous record
let value-name = [struct] {| Label1: Type1; Label2: Type2; ...|}
// Use an anonymous record as a type parameter
let value-name = Type-Name<[struct] {| Label1: Type1; Label2: Type2; ...|}>
// Define a parameter with an anonymous record as input
let function-name (arg-name: [struct] {| Label1: Type1; Label2: Type2; ...|}) ...
Základní použití
Anonymní záznamy se nejlépe považují za typy záznamů jazyka F#, které před vytvořením instance nemusí být deklarovány.
Tady je příklad, jak můžete pracovat s funkcí, která vytváří anonymní záznam:
open System
let getCircleStats 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 = getCircleStats r
printfn "Circle with radius: %f has diameter %f, area %f, and circumference %f"
r stats.Diameter stats.Area stats.Circumference
Následující příklad rozbalí předchozí printCircleStats
s funkcí, která přebírá anonymní záznam jako vstup:
open System
let getCircleStats 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 = getCircleStats r
printCircleStats r stats
Volání printCircleStats
s libovolným anonymním typem záznamu, který nemá stejný tvar jako vstupní typ, se nepodaří zkompilovat:
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"]'
Struktury anonymních záznamů
Anonymní záznamy lze také definovat jako strukturu s volitelným struct
klíčovým slovem. Následující příklad rozšiřuje předchozí záznam o vytvoření a využívání struktury anonymního záznamu:
open System
let getCircleStats 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 = getCircleStats r
printCircleStats r stats
Odvození struktury
Anonymní záznamy struktury také umožňují "odvození struktury", kde není nutné zadat struct
klíčové slovo v lokalitě volání. V tomto příkladu struct
klíčové slovo při volání :printCircleStats
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 |}
Obrácený vzor – určení struct
, kdy vstupní typ není anonymní záznam struktury – se nepodaří zkompilovat.
Vkládání anonymních záznamů do jiných typů
Je užitečné deklarovat diskriminované sjednocení , jejichž případy jsou záznamy. Pokud jsou ale data v záznamech stejného typu jako diskriminovaná sjednocení, musíte definovat všechny typy jako vzájemně rekurzivní. Použití anonymních záznamů zabrání tomuto omezení. Následuje příklad typu a funkce, která se shoduje se vzorem:
type FullName = { FirstName: string; LastName: string }
// Note that using a named record for Manager and Executive would require mutually recursive definitions.
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
Kopírování a aktualizace výrazů
Anonymní záznamy podporují vytváření pomocí výrazů kopírování a aktualizace. Tady je příklad, jak můžete vytvořit novou instanci anonymního záznamu, který kopíruje data existujícího záznamu:
let data = {| X = 1; Y = 2 |}
let data' = {| data with Y = 3 |}
Na rozdíl od pojmenovaných záznamů však anonymní záznamy umožňují vytvářet zcela různé formuláře pomocí výrazů kopírování a aktualizace. Následující příklad vezme stejný anonymní záznam z předchozího příkladu a rozbalí ho na nový anonymní záznam:
let data = {| X = 1; Y = 2 |}
let expandedData = {| data with Z = 3 |} // Gives {| X=1; Y=2; Z=3 |}
Je také možné vytvořit anonymní záznamy z instancí pojmenovaných záznamů:
type R = { X: int }
let data = { X = 1 }
let data' = {| data with Y = 2 |} // Gives {| X=1; Y=2 |}
Můžete také kopírovat data do a z odkazů a strukturovat anonymní záznamy:
// 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 |}
// Copy the reference anonymous record data into a struct anonymous record
let data3 = struct {| data2 with Z = r2.X |}
Vlastnosti anonymních záznamů
Anonymní záznamy mají řadu charakteristik, které jsou nezbytné k úplnému porozumění způsobu jejich použití.
Anonymní záznamy jsou nominální
Anonymní záznamy jsou nominální typy. Nejlépe se považují za pojmenované typy záznamů (které jsou také jmenovité), které nevyžadují počáteční deklaraci.
Představte si následující příklad se dvěma deklaracemi anonymních záznamů:
let x = {| X = 1 |}
let y = {| Y = 1 |}
y
Hodnoty x
mají různé typy a nejsou vzájemně kompatibilní. Nejsou vyrovnané a nejsou srovnatelné. Abyste to mohli ilustrovat, zvažte ekvivalent pojmenovaného záznamu:
type X = { X: int }
type Y = { Y: int }
let x = { X = 1 }
let y = { Y = 1 }
Anonymní záznamy se v porovnání s jejich pojmenovanými ekvivalenty záznamů, pokud jde o ekvivalenci nebo porovnání typu, nijak neliší.
Anonymní záznamy používají strukturální rovnost a porovnání
Stejně jako typy záznamů jsou anonymní záznamy strukturálně equatable a srovnatelné. To platí pouze v případě, že všechny typy složek podporují rovnost a porovnávání, jako u typů záznamů. Pro podporu rovnosti nebo porovnání musí mít dva anonymní záznamy stejný "tvar".
{| a = 1+1 |} = {| a = 2 |} // true
{| a = 1+1 |} > {| a = 1 |} // true
// error FS0001: Two anonymous record types have mismatched sets of field names '["a"]' and '["a"; "b"]'
{| a = 1 + 1 |} = {| a = 2; b = 1|}
Anonymní záznamy jsou serializovatelné
Anonymní záznamy můžete serializovat stejně jako u pojmenovaných záznamů. Tady je příklad použití Newtonsoft.Json:
open Newtonsoft.Json
let phillip' = {| name="Phillip"; age=28 |}
let philStr = JsonConvert.SerializeObject(phillip')
let phillip = JsonConvert.DeserializeObject<{|name: string; age: int|}>(philStr)
printfn $"Name: {phillip.name} Age: %d{phillip.age}"
Anonymní záznamy jsou užitečné pro odesílání jednoduchých dat přes síť bez nutnosti definovat doménu pro serializované nebo deserializované typy předem.
Anonymní záznamy interoperabilní s anonymními typy jazyka C#
Je možné použít rozhraní .NET API, které vyžaduje použití anonymních typů jazyka C#. Anonymní typy jazyka C# jsou triviální pro spolupráci pomocí anonymních záznamů. Následující příklad ukazuje, jak použít anonymní záznamy k volání přetížení LINQ , které vyžaduje anonymní typ:
open System.Linq
let names = [ "Ana"; "Felipe"; "Emilia"]
let nameGrouping = names.Select(fun n -> {| Name = n; FirstLetter = n[0] |})
for ng in nameGrouping do
printfn $"{ng.Name} has first letter {ng.FirstLetter}"
V rozhraní .NET se používá celá řada dalších rozhraní API, která vyžadují předání anonymního typu. Anonymní záznamy jsou vaším nástrojem pro práci s nimi.
Omezení
Anonymní záznamy mají v jejich používání určitá omezení. Některé jsou ze své podstaty návrhu, ale jiné jsou srozumitelné ke změně.
Omezení při porovnávání vzorů
Anonymní záznamy nepodporují porovnávání vzorů, na rozdíl od pojmenovaných záznamů. Existují tři důvody:
- Vzor by musel zohledňovat každé pole anonymního záznamu, na rozdíl od pojmenovaných typů záznamů. Je to proto, že anonymní záznamy nepodporují strukturální podtypy – jedná se o nominální typy.
- Vzhledem k (1) neexistuje možnost mít ve výrazu shody vzorů další vzory, protože každý odlišný vzor by znamenal jiný anonymní typ záznamu.
- Z důvodu (2) by jakýkoli anonymní vzor záznamu byl více podrobný než použití zápisu "tečka".
Existuje návrh otevřeného jazyka, který umožňuje porovnávání vzorů v omezených kontextech.
Omezení s proměnlivostí
V současné době není možné definovat anonymní záznam s mutable
daty. Existuje návrh otevřeného jazyka , který umožňuje měnitelná data.
Omezení s anonymními záznamy struktury
Není možné deklarovat strukturu anonymních záznamů jako IsByRefLike
nebo IsReadOnly
. Existuje návrh otevřeného jazyka pro IsByRefLike
a IsReadOnly
anonymní záznamy.