Enregistrements anonymes

Les enregistrements anonymes sont des agrégats simples de valeurs nommées qui n’ont pas besoin d’être déclarés avant d’être utilisés. Vous pouvez les déclarer en tant que types structs ou référence. Il s’agit de types référence par défaut.

Syntaxe

Les exemples suivants illustrent la syntaxe d’enregistrement anonyme. Les éléments délimités comme [item] sont facultatifs.

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

Utilisation de base

Les enregistrements anonymes sont mieux considérés comme des types d’enregistrements F# qui n’ont pas besoin d’être déclarés avant l’instanciation.

Par exemple, voici comment interagir avec une fonction qui produit un enregistrement anonyme :

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

L’exemple suivant développe le précédent avec une fonction qui prend un enregistrement anonyme printCircleStats comme entrée :

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

L’appel de printCircleStats avec n’importe quel type d’enregistrement anonyme qui n’a pas la même « forme » que le type d’entrée ne parvient pas à compiler :

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

Enregistrements anonymes struct

Les enregistrements anonymes peuvent également être définis en tant que struct avec le mot clé facultatif struct. L’exemple suivant développe le précédent en produisant et en consommant un enregistrement anonyme de 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

Inférence de struct

Les enregistrements anonymes de struct autorisent également l’« inférence de struct » où vous n’avez pas besoin de spécifier le mot clé struct sur le site d’appel. Dans cet exemple, vous omettez le mot clé struct lors de l’appel de 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 |}

Le modèle inverse, à savoir spécifier struct quand le type d’entrée n’est pas un enregistrement anonyme struct, ne peut pas être compilé.

Incorporation d’enregistrements anonymes dans d’autres types

Il est utile de déclarer des unions discriminées dont les cas sont des enregistrements. Toutefois, si les données des enregistrements sont du même type que l’union discriminée, vous devez définir tous les types comme étant mutuellement récursifs. L’utilisation d’enregistrements anonymes évite cette restriction. Voici un exemple de type et de fonction correspondant au modèle :

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

Copier et mettre à jour des expressions

Les enregistrements anonymes prennent en charge la construction avec des expressions de copie et de mise à jour. Par exemple, voici comment construire une nouvelle instance d’un enregistrement anonyme qui copie les données d’un enregistrement existant :

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

Toutefois, contrairement aux enregistrements nommés, les enregistrements anonymes vous permettent de construire des formes entièrement différentes avec des expressions de copie et de mise à jour. L’exemple suivant prend le même enregistrement anonyme de l’exemple précédent et le développe en un nouvel enregistrement anonyme :

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

Il est également possible de construire des enregistrements anonymes à partir d’instances d’enregistrements nommés :

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

Vous pouvez également copier des données vers et depuis des enregistrements anonymes de référence et struct :

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

Propriétés des enregistrements anonymes

Les enregistrements anonymes ont un certain nombre de caractéristiques qui sont essentielles pour bien comprendre comment ils peuvent être utilisés.

Les enregistrements anonymes sont nominaux

Les enregistrements anonymes sont des types nominaux. Ils doivent être considérés comme des types d’enregistrements nommés (qui sont également nominaux) qui ne nécessitent pas de déclaration préalable.

Prenons l’exemple suivant avec deux déclarations d’enregistrement anonymes :

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

Les valeurs x et y ont des types différents et ne sont pas compatibles entre elles. Elles ne sont pas comparables et ne peuvent pas faire l’objet d’une équivalence. Pour illustrer cela, considérez un enregistrement nommé équivalent :

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

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

Il n’y a rien de différent intrinsèquement entre les enregistrements anonymes et leurs équivalents en enregistrements nommés en ce qui concerne l’équivalence de type ou la comparaison.

Les enregistrements anonymes utilisent l’égalité structurelle et la comparaison

Comme les types d’enregistrements, les enregistrements anonymes sont structurellement comparables et peuvent faire l’objet d’une équivalence. Cela n’est vrai que si tous les types constitutifs prennent en charge l’équivalence et la comparaison, comme avec les types d’enregistrements. Pour prendre en charge l’équivalence ou la comparaison, deux enregistrements anonymes doivent avoir la même « forme ».

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

Les enregistrements anonymes sont sérialisables

Vous pouvez sérialiser des enregistrements anonymes comme vous le pouvez avec des enregistrements nommés. Voici un exemple utilisant 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}"

Les enregistrements anonymes sont utiles pour envoyer des données légères sur un réseau sans avoir à définir de domaine pour vos types sérialisés/désérialisés à l’avance.

Les enregistrements anonymes interagissent avec les types anonymes C#

Il est possible d’utiliser une API .NET qui nécessite l’utilisation de types anonymes C#. Les types anonymes C# offrent une interopérabilité triviale grâce aux enregistrements anonymes. L’exemple suivant montre comment utiliser des enregistrements anonymes pour appeler une surcharge LINQ qui nécessite un type anonyme :

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

Il existe une multitude d’autres API utilisées dans .NET qui nécessitent l’utilisation du passage d’un type anonyme. Les enregistrements anonymes sont votre outil pour les utiliser.

Limites

Les enregistrements anonymes ont certaines restrictions dans leur utilisation. Certaines sont inhérentes à leur conception, mais d’autres peuvent être modifiées.

Limitations avec la correspondance de modèle

Les enregistrements anonymes ne prennent pas en charge la correspondance de modèle, contrairement aux enregistrements nommés. Il existe trois raisons à cela :

  1. Un modèle doit prendre en compte chaque champ d’un enregistrement anonyme, contrairement aux types d’enregistrements nommés. Cela est dû au fait que les enregistrements anonymes ne prennent pas en charge le sous-typage structurel : il s’agit de types nominaux.
  2. En raison de (1), il n’est pas possible d’avoir d’autres modèles dans une expression de correspondance de modèle, car chaque modèle distinct impliquerait un type d’enregistrement anonyme différent.
  3. En raison de (2), tout modèle d’enregistrement anonyme serait plus détaillé que l’utilisation de la notation « point ».

Il existe une suggestion de langage ouverte pour autoriser la correspondance de modèle dans des contextes limités.

Limitations de la mutabilité

Il n’est actuellement pas possible de définir un enregistrement anonyme avec des données mutable. Il existe une suggestion de langage ouverte pour autoriser les données mutables.

Limitations avec les enregistrements anonymes struct

Il n’est pas possible de déclarer des enregistrements anonymes struct en tant que IsByRefLike ou IsReadOnly. Il existe une suggestion de langue ouverte pour les enregistrements anonymes IsByRefLike et IsReadOnly.