Анонимные записи
Анонимные записи — это простые агрегаты именованных значений, которые не нужно объявлять перед использованием. Их можно объявить как структуры или ссылочные типы. По умолчанию они ссылаются на типы.
Синтаксис
В следующих примерах показан синтаксис анонимной записи. Элементы, разделенные как [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 |}
Значения x
y
имеют разные типы и несовместимы друг с другом. Они не являются удваиваемыми, и они не сопоставимы. Чтобы проиллюстрировать это, рассмотрим именованный эквивалент записи:
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) любая анонимная запись будет более подробной, чем использование нотации dot.
Существует открытое предложение языка для разрешения сопоставления шаблонов в ограниченных контекстах.
Ограничения с изменяемостью
В настоящее время невозможно определить анонимную запись с mutable
данными. Существует открытое предложение языка, позволяющее изменять изменяемые данные.
Ограничения с анонимными записями структуры
Невозможно объявить анонимные записи структуры как IsByRefLike
или IsReadOnly
. Существует открытое предложение по языку для IsByRefLike
и IsReadOnly
анонимных записей.