Registros (F#)

Los registros representan agregados simples de valores con nombre, opcionalmente con miembros. Pueden ser estructuras o tipos de referencia. Son tipos de referencia de forma predeterminada.

Sintaxis

[ attributes ]
type [accessibility-modifier] typename =
    { [ mutable ] label1 : type1;
      [ mutable ] label2 : type2;
      ... }
    [ member-list ]

Comentarios

En la sintaxis anterior, typename es el nombre del tipo de registro, label1 y label2 son nombres de valores, denominados etiquetas, y type1 y type2 son los tipos de estos valores. member-list es la lista opcional de miembros para el tipo. Puede usar el atributo [<Struct>] para crear un registro de estructura en lugar de un registro que sea un tipo de referencia.

A continuación se muestran algunos ejemplos.

// Labels are separated by semicolons when defined on the same line.
type Point = { X: float; Y: float; Z: float }

// You can define labels on their own line with or without a semicolon.
type Customer =
    { First: string
      Last: string
      SSN: uint32
      AccountNumber: uint32 }

// A struct record.
[<Struct>]
type StructPoint = { X: float; Y: float; Z: float }

Cuando cada etiqueta está en una línea independiente, el punto y coma es opcional.

Puede establecer valores en expresiones conocidas como expresiones de registro. El compilador deduce el tipo de las etiquetas usadas (si las etiquetas son suficientemente distintas de las de otros tipos de registro). Las llaves ({ }) encierran la expresión de registro. El código siguiente muestra una expresión de registro que inicializa un registro con tres elementos float con etiquetas x, y y z.

let mypoint = { X = 1.0; Y = 1.0; Z = -1.0 }

No use el formulario abreviado si podría haber otro tipo que también tenga las mismas etiquetas.

type Point = { X: float; Y: float; Z: float }
type Point3D = { X: float; Y: float; Z: float }
// Ambiguity: Point or Point3D?
let mypoint3D = { X = 1.0; Y = 1.0; Z = 0.0 }

Las etiquetas del tipo declarado más recientemente tienen prioridad sobre las del tipo declarado anteriormente, por lo que, en el ejemplo anterior, se deduce que mypoint3D es Point3D. Puede especificar explícitamente el tipo de registro, como en el código siguiente.

let myPoint1 = { Point.X = 1.0; Y = 1.0; Z = 0.0 }

Los métodos se pueden definir para los tipos de registro igual que para los tipos de clase.

Creación de registros mediante expresiones de registro

Puede inicializar registros mediante las etiquetas definidas en el registro. Una expresión que hace esto se conoce como una expresión de registro. Use llaves para incluir la expresión de registro y use el punto y coma como delimitador.

En el ejemplo siguiente se muestra cómo crear un registro.

type MyRecord = { X: int; Y: int; Z: int }

let myRecord1 = { X = 1; Y = 2; Z = 3 }

Los puntos y coma después del último campo de la expresión de registro y en la definición de tipo son opcionales, independientemente de si los campos están todos en una línea.

Al crear un registro, debe proporcionar valores para cada campo. No puede hacer referencia a los valores de otros campos de la expresión de inicialización para ningún campo.

En el código siguiente, el tipo de myRecord2 se deduce de los nombres de los campos. Opcionalmente, puede especificar el nombre de tipo explícitamente.

let myRecord2 =
    { MyRecord.X = 1
      MyRecord.Y = 2
      MyRecord.Z = 3 }

Tener otra forma de construcción de registros puede ser útil si hay que copiar un registro existente y, posiblemente, cambiar algunos de los valores de los campos. Esto se ilustra en la línea de código siguiente.

let myRecord3 = { myRecord2 with Y = 100; Z = 2 }

Esta forma de la expresión de registro se denomina expresión de registro de copia y actualización.

Los registros son inmutables de forma predeterminada; sin embargo, puede crear fácilmente registros modificados mediante una expresión de copia y actualización. También puede especificar explícitamente un campo mutable.

type Car =
    { Make: string
      Model: string
      mutable Odometer: int }

let myCar =
    { Make = "Fabrikam"
      Model = "Coupe"
      Odometer = 108112 }

myCar.Odometer <- myCar.Odometer + 21

No use el atributo DefaultValue con campos de registro. Un mejor enfoque es definir instancias predeterminadas de registros con campos que se inicializan en valores predeterminados y, a continuación, usar una expresión de registro de copia y actualización para establecer los campos que difieren de los valores predeterminados.

// Rather than use [<DefaultValue>], define a default record.
type MyRecord =
    { Field1 : int
      Field2 : int }

let defaultRecord1 = { Field1 = 0; Field2 = 0 }
let defaultRecord2 = { Field1 = 1; Field2 = 25 }

// Use the with keyword to populate only a few chosen fields
// and leave the rest with default values.
let rr3 = { defaultRecord1 with Field2 = 42 }

Crear registros mutuamente recursivos

En algún momento al crear un registro, es posible que quiera que dependa de otro tipo que desee definir después. Se trata de un error de compilación a menos que defina los tipos de registro que se van a repetir mutuamente.

La definición de registros recursivos mutuamente se realiza con la palabra clave and. Esto le permite vincular 2 o más tipos de registros juntos.

Por ejemplo, el código siguiente define un tipo Person y Address como recursivo mutuamente:

// Create a Person type and use the Address type that is not defined
type Person =
  { Name: string
    Age: int
    Address: Address }
// Define the Address type which is used in the Person record
and Address =
  { Line1: string
    Line2: string
    PostCode: string
    Occupant: Person }

Para crear instancias de ambos, haga lo siguiente:

// Create a Person type and use the Address type that is not defined
let rec person =
  {
      Name = "Person name"
      Age = 12
      Address =
          {
              Line1 = "line 1"
              Line2 = "line 2"
              PostCode = "abc123"
              Occupant = person
          }
  }

Si tuviera que definir el ejemplo anterior sin la palabra clave and, no se compilaría. La palabra clave and es necesaria para las definiciones recursivas mutuamente.

Coincidencia de patrones con registros

Los registros se pueden usar con la coincidencia de patrones. Puede especificar algunos campos explícitamente y proporcionar variables para otros campos que se asignarán cuando se produzca una coincidencia. En el siguiente ejemplo código se muestra cómo hacerlo.

type Point3D = { X: float; Y: float; Z: float }
let evaluatePoint (point: Point3D) =
    match point with
    | { X = 0.0; Y = 0.0; Z = 0.0 } -> printfn "Point is at the origin."
    | { X = xVal; Y = 0.0; Z = 0.0 } -> printfn "Point is on the x-axis. Value is %f." xVal
    | { X = 0.0; Y = yVal; Z = 0.0 } -> printfn "Point is on the y-axis. Value is %f." yVal
    | { X = 0.0; Y = 0.0; Z = zVal } -> printfn "Point is on the z-axis. Value is %f." zVal
    | { X = xVal; Y = yVal; Z = zVal } -> printfn "Point is at (%f, %f, %f)." xVal yVal zVal

evaluatePoint { X = 0.0; Y = 0.0; Z = 0.0 }
evaluatePoint { X = 100.0; Y = 0.0; Z = 0.0 }
evaluatePoint { X = 10.0; Y = 0.0; Z = -1.0 }

El resultado de este código es el siguiente.

Point is at the origin.
Point is on the x-axis. Value is 100.000000.
Point is at (10.000000, 0.000000, -1.000000).

Registros y miembros

Puede especificar miembros en registros de forma muy similar a las clases. No se admiten los campos. Un enfoque común es definir un miembro estático Default para facilitar la construcción de registros:

type Person =
  { Name: string
    Age: int
    Address: string }

    static member Default =
        { Name = "Phillip"
          Age = 12
          Address = "123 happy fun street" }

let defaultPerson = Person.Default

Si usa un identificador propio, ese identificador hace referencia a la instancia del registro cuyo miembro se llama:

type Person =
  { Name: string
    Age: int
    Address: string }

    member this.WeirdToString() =
        this.Name + this.Address + string this.Age

let p = { Name = "a"; Age = 12; Address = "abc123" }
let weirdString = p.WeirdToString()

Diferencias entre registros y clases

Los campos de registro difieren de los campos de clase porque se exponen automáticamente como propiedades y se usan en la creación y copia de registros. La construcción de registros también es diferente de la construcción de clases. En un tipo de registro, no se puede definir un constructor. En su lugar, se aplica la sintaxis de construcción descrita en este tema. Las clases no tienen ninguna relación directa entre los parámetros, los campos y las propiedades del constructor.

Al igual que los tipos de unión y estructura, los registros tienen semántica de igualdad estructural. Las clases tienen semántica de igualdad de referencia. El siguiente ejemplo de código muestra esto.

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

let record1 = { X = 1; Y = 2 }
let record2 = { X = 1; Y = 2 }

if (record1 = record2) then
    printfn "The records are equal."
else
    printfn "The records are unequal."

El resultado de este código es el siguiente:

The records are equal.

Si escribe el mismo código con clases, los dos objetos de clase no serían iguales porque los dos valores representarían dos objetos del montón y solo se compararían las direcciones (a menos que el tipo de clase invalide el método System.Object.Equals).

Si necesita igualdad de referencia para los registros, agregue el atributo [<ReferenceEquality>] encima del registro.

Consulte también