Rekordy anonimowe

Rekordy anonimowe to proste agregacje nazwanych wartości, które nie muszą być zadeklarowane przed użyciem. Można je zadeklarować jako struktury lub typy referencyjne. Domyślnie są to typy referencyjne.

Składnia

W poniższych przykładach przedstawiono składnię rekordów anonimowych. Elementy rozdzielone [item] jako są opcjonalne.

// 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; ...|}) ...

Podstawowy sposób użycia

Rekordy anonimowe najlepiej traktować jako typy rekordów języka F#, które nie muszą być deklarowane przed utworzeniem wystąpienia.

Na przykład w tym miejscu możesz wchodzić w interakcje z funkcją, która generuje rekord anonimowy:

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

Poniższy przykład rozszerza poprzedni z funkcją printCircleStats , która przyjmuje anonimowy rekord jako dane wejściowe:

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

Wywołanie przy printCircleStats użyciu dowolnego typu rekordu anonimowego, który nie ma takiego samego kształtu, jak typ wejściowy, nie powiedzie się skompilować:

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"]'

Tworzenie anonimowych rekordów

Rekordy anonimowe można również zdefiniować jako strukturę za pomocą opcjonalnego struct słowa kluczowego. Poniższy przykład rozszerza poprzedni, tworząc i zużywając anonimowy rekord struktury:

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

Wnioskowanie struktury

Rekordy anonimowe struktury umożliwiają również "wnioskowanie struktury", w którym nie trzeba określać struct słowa kluczowego w witrynie wywołania. W tym przykładzie wyjedziesz słowo struct kluczowe podczas wywoływania elementu 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 |}

Wzorzec odwrotny — określający struct , kiedy typ danych wejściowych nie jest rekordem anonimowym struktury — nie będzie można skompilować.

Osadzanie rekordów anonimowych w innych typach

Warto deklarować dyskryminowane związki zawodowe, których przypadki są zapisami . Jeśli jednak dane w rekordach są tego samego typu co związek dyskryminowany, należy zdefiniować wszystkie typy jako wzajemnie rekursywne. Używanie rekordów anonimowych pozwala uniknąć tego ograniczenia. Poniżej znajduje się przykładowy typ i funkcja zgodna ze wzorcem:

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

Kopiowanie i aktualizowanie wyrażeń

Rekordy anonimowe obsługują konstruowanie za pomocą wyrażeń kopiowania i aktualizowania. Poniżej przedstawiono na przykład sposób konstruowania nowego wystąpienia rekordu anonimowego, które kopiuje istniejące dane:

let data = {| X = 1; Y = 2 |}
let data' = {| data with Y = 3 |}

Jednak w przeciwieństwie do nazwanych rekordów, rekordy anonimowe umożliwiają konstruowanie zupełnie różnych formularzy za pomocą wyrażeń kopiowania i aktualizowania. Poniższy przykład przyjmuje ten sam anonimowy rekord z poprzedniego przykładu i rozszerza go do nowego rekordu anonimowego:

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

Istnieje również możliwość konstruowania anonimowych rekordów z wystąpień nazwanych rekordów:

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

Można również kopiować dane do i z odwołań i struktur anonimowych rekordów:

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

Właściwości rekordów anonimowych

Rekordy anonimowe mają wiele cech, które są niezbędne do pełnego zrozumienia sposobu ich użycia.

Rekordy anonimowe są nominalne

Rekordy anonimowe to nominalne typy. Najlepiej traktować je jako nazwane typy rekordów (które są również nominalne), które nie wymagają deklaracji z góry.

Rozważmy następujący przykład z dwoma deklaracjami rekordów anonimowych:

let x = {| X = 1 |}
let y = {| Y = 1 |}

Wartości x i y mają różne typy i nie są ze sobą zgodne. Nie są one kłótnie i nie są porównywalne. Aby to zilustrować, rozważ odpowiednik nazwanego rekordu:

type X = { X: int }
type Y = { Y: int }

let x = { X = 1 }
let y = { Y = 1 }

Nie ma z natury nic innego w przypadku rekordów anonimowych w porównaniu z ich nazwanymi odpowiednikami rekordów w przypadku równoważności lub porównania typów.

Rekordy anonimowe używają równości strukturalnej i porównania

Podobnie jak w przypadku typów rekordów, rekordy anonimowe są strukturalne i porównywalne. Ma to wartość tylko wtedy, gdy wszystkie typy składowe obsługują równość i porównanie, podobnie jak w przypadku typów rekordów. Aby zapewnić równość lub porównanie, dwa anonimowe rekordy muszą mieć taki sam "kształt".

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

Rekordy anonimowe można serializować

Rekordy anonimowe można serializować tak samo jak w przypadku nazwanych rekordów. Oto przykład użycia pliku 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}"

Rekordy anonimowe są przydatne do wysyłania uproszczonych danych przez sieć bez konieczności definiowania domeny dla serializacji/deserializacji typów z góry.

Rekordy anonimowe współdziałają z typami anonimowymi języka C#

Można użyć interfejsu API platformy .NET, który wymaga użycia typów anonimowych języka C#. Typy anonimowe języka C# są proste do współdziałania z użyciem rekordów anonimowych. W poniższym przykładzie pokazano, jak używać rekordów anonimowych do wywoływania przeciążenia LINQ , które wymaga typu anonimowego:

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

Istnieje wiele innych interfejsów API używanych na platformie .NET, które wymagają użycia przekazywania typu anonimowego. Rekordy anonimowe to narzędzie do pracy z nimi.

Ograniczenia

Rekordy anonimowe mają pewne ograniczenia dotyczące ich użycia. Niektóre są związane z ich projektem, ale inne mogą ulec zmianie.

Ograniczenia dotyczące dopasowywania wzorców

Rekordy anonimowe nie obsługują dopasowywania wzorców, w przeciwieństwie do nazwanych rekordów. Istnieją trzy powody:

  1. Wzorzec musi uwzględniać każde pole rekordu anonimowego, w przeciwieństwie do nazwanych typów rekordów. Dzieje się tak, ponieważ rekordy anonimowe nie obsługują podtypowania strukturalnego — są to typy nominalne.
  2. Ze względu na (1) nie ma możliwości posiadania dodatkowych wzorców w wyrażeniu dopasowania wzorca, ponieważ każdy odrębny wzorzec oznacza inny typ rekordu anonimowego.
  3. Ze względu na (2) każdy wzorzec rekordu anonimowego byłby bardziej szczegółowy niż użycie notacji "kropka".

Istnieje otwarta sugestia języka umożliwiająca dopasowywanie wzorców w ograniczonych kontekstach.

Ograniczenia dotyczące niezmienności

Obecnie nie można zdefiniować anonimowego rekordu z mutable danymi. Istnieje otwarta sugestia języka umożliwiająca modyfikowalne dane.

Ograniczenia dotyczące rekordów anonimowych struktury

Nie można zadeklarować struktury anonimowych rekordów jako IsByRefLike lub IsReadOnly. Istnieje otwarta sugestia języka dla IsByRefLike rekordów anonimowych i IsReadOnly rekordów anonimowych.