Анонимные записи

Анонимные записи — это простые агрегаты именованных значений, которые не нужно объявлять перед использованием. Их можно объявить как структуры или ссылочные типы. По умолчанию они ссылаются на типы.

Синтаксис

В следующих примерах показан синтаксис анонимной записи. Элементы, разделенные как [item] необязательные.

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

Базовое использование

Анонимные записи лучше всего считаются типами записей F#, которые не нужно объявлять перед созданием экземпляров.

Например, здесь можно взаимодействовать с функцией, которая создает анонимную запись:

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

Следующий пример расширяет предыдущий с printCircleStats функцией, которая принимает анонимную запись в качестве входных данных:

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

Вызов printCircleStats с любым анонимным типом записи, который не имеет той же "фигуры", что и входной тип, не будет компилироваться:

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

Струк анонимные записи

Анонимные записи также можно определить как структуру с необязательным struct ключевое слово. Следующий пример расширяет предыдущий, создавая и потребляя анонимную запись структуры:

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

Вывод структуры

Анонимные записи структуры также позволяют "вывод структуры", где не нужно указывать struct ключевое слово на сайте вызова. В этом примере вы скользите struct ключевое слово при вызове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 |}

Обратный шаблон, указывающий struct , когда входной тип не является анонимной записью структуры, не будет компилироваться.

Внедрение анонимных записей в другие типы

Полезно объявить дискриминированные профсоюзы , дела которых являются записями. Но если данные в записях совпадают с типом различаемого объединения, необходимо определить все типы как взаимокурсивные. Использование анонимных записей избегает этого ограничения. Ниже приведен пример типа и функции, которые соответствуют шаблону.

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

Копирование и обновление выражений

Анонимные записи поддерживают построение с помощью выражений копирования и обновления. Например, вот как создать новый экземпляр анонимной записи, которая копирует существующие данные:

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

Однако в отличие от именованных записей анонимные записи позволяют создавать совершенно разные формы с выражениями копирования и обновления. Следующий пример принимает ту же анонимную запись из предыдущего примера и расширяет ее в новую анонимную запись:

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

Также можно создавать анонимные записи из экземпляров именованных записей:

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

Вы также можете копировать данные в ссылки и из ссылок и структурировать анонимные записи:

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

Свойства анонимных записей

Анонимные записи имеют ряд характеристик, необходимых для полного понимания того, как они могут использоваться.

Анонимные записи являются номинальными

Анонимные записи — это номинальные типы. Они лучше всего считаются именованными типами записей (которые также являются номинальными), которые не требуют предварительного объявления.

Рассмотрим следующий пример с двумя объявлениями анонимной записи:

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

Значения xy имеют разные типы и несовместимы друг с другом. Они не являются удваиваемыми, и они не сопоставимы. Чтобы проиллюстрировать это, рассмотрим именованный эквивалент записи:

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

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

По сравнению с их именованной записью при сравнении с эквивалентами именованных записей не существует ничего, что касается эквивалентности типа или сравнения.

Анонимные записи используют структурное равенство и сравнение

Как и типы записей, анонимные записи являются структурны удваиваемыми и сопоставимыми. Это верно, только если все составляющие типы поддерживают равенство и сравнение, как и с типами записей. Для поддержки равенства или сравнения две анонимные записи должны иметь одну и ту же "фигуру".

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

Анонимные записи сериализуются

Анонимные записи можно сериализовать так же, как и с именованных записей. Ниже приведен пример использования 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}"

Анонимные записи полезны для отправки упрощенных данных по сети без необходимости определить домен для сериализованных или десериализированных типов заранее.

Анонимные записи взаимодействуют с анонимными типами C#

Можно использовать API .NET, требующий использования анонимных типов C#. Анонимные типы C# являются тривиальными для взаимодействия с использованием анонимных записей. В следующем примере показано, как использовать анонимные записи для вызова перегрузки LINQ , требующей анонимного типа:

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

Существует множество других API, используемых в .NET, которые требуют использования анонимного типа. Анонимные записи — это средство для работы с ними.

Ограничения

Анонимные записи имеют некоторые ограничения в их использовании. Некоторые из них присущи их дизайну, но другие можно изменить.

Ограничения при сопоставлении шаблонов

Анонимные записи не поддерживают сопоставление шаблонов, в отличие от именованных записей. Существует три причины:

  1. Шаблон должен учитывать каждое поле анонимной записи, в отличие от именованных типов записей. Это связано с тем, что анонимные записи не поддерживают структурную подтипность — они являются номинальными типами.
  2. Из-за (1) нет возможности иметь дополнительные шаблоны в выражении сопоставления шаблонов, так как каждый отдельный шаблон подразумевает другой анонимный тип записи.
  3. Из-за (2) любая анонимная запись будет более подробной, чем использование нотации dot.

Существует открытое предложение языка для разрешения сопоставления шаблонов в ограниченных контекстах.

Ограничения с изменяемостью

В настоящее время невозможно определить анонимную запись с mutable данными. Существует открытое предложение языка, позволяющее изменять изменяемые данные.

Ограничения с анонимными записями структуры

Невозможно объявить анонимные записи структуры как IsByRefLike или IsReadOnly. Существует открытое предложение по языку для IsByRefLike и IsReadOnly анонимных записей.