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:

  1. 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.
  2. 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.
  3. 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.