Records

Presentábamos antes la necesidad de agregar información útil de algunas entidades que no se puede restringir a tener unos tipos específicos, por ejemplo, los datos de una tarjeta de crédito.

Prácticamente cualquier lenguaje de alto nivel proporciona una forma de definir dichos tipos. En lenguaje C uno tiene el tipo struct, en Python una clase sin métodos (o una tupla con nombre), etc. Este tipo en F# se denomina record.

type CreditCard =
    {
        HoldersName : string
        Number: string
        ExpirationDateMonth: uint8 
        ExpirationDateYear: uint8 
        CVV: uint16
    }

El tipo record utiliza etiquetas para agregar los diferentes componentes del tipo. Cada componente tiene una etiqueta (HoldersName, Number, etc.) y un tipo asociado a él. Para crear un tipo record, es necesario definir todos y cada uno de los componentes:

let doeCard = 
    {
        HoldersName = "John Doe"
        Number = "1234 5678 9101 1121"
        ExpirationDateMonth = 12uy
        ExpirationDateYear = 23uy 
        CVV = 111us 
    }

🔔 Tenga en cuenta el sufijo uy para enteros sin signo de 8 bits (uint8) y us para su compañero de 16 bits (uint16).

En lugar de indentar la definición, uno puede escribir todos los componentes juntos, separados por ;:

let doeFakeCard = { HoldersName = "John Doe"; Number = "1234 5678 9101 1121"; ExpirationDateMonth = 12uy; ExpirationDateYear = 23uy ; CVV = 111us}

printfn "%A" doeFakeCard    
{ HoldersName = "John Doe"
  Number = "1234 5678 9101 1121"
  ExpirationDateMonth = 12uy
  ExpirationDateYear = 23uy
  CVV = 111us }

pero como se puede ver, esto es adecuado solo para records pequeños.

¿Qué sucede si la tarjeta de John Doe vence y necesita ser reemplazada por una nueva? Al igual que con todos los demás valores en el idioma, los records son inmutables, por lo que no es posible actualizar doeCard en su lugar. Para hacer eso, necesitamos crear otro valor nuevo. F# proporciona una forma de copiar y actualizar un valor del registro, que nos permite cambiar solo los componentes que deben cambiarse en un record. Suponiendo que la nueva tarjeta mantenga el número (y, por supuesto, el nombre del titular de la tarjeta), tendríamos:

let newDoeCard = 
    { doeCard with 
        ExpirationDateMonth = 12uy
        ExpirationDateYear = 25uy
        CVV = 222us 
    }

printfn "%A" newDoeCard    
{ HoldersName = "John Doe"
  Number = "1234 5678 9101 1121"
  ExpirationDateMonth = 12uy
  ExpirationDateYear = 25uy
  CVV = 222us }

Uno usa nuevamente etiquetas para expresar el tipo de record. De esta manera se crea un nuevo registro utilizando el valor antiguo doeCard usando with para identificar los componentes que necesitan ser actualizados.

Para acceder a un componente específico de un record, se usa nuevamente ., como hicimos con las uniones discriminadas:

printfn "John's Does card number: %A" newDoeCard.Number 
printfn "John's Does card CVV: %A" newDoeCard.CVV
John's Does card number: "1234 5678 9101 1121"
John's Does card CVV: 222us

Mezclando tipos

La unión discriminada y el record son las dos formas en que uno puede representar entidades en el lenguaje. Uno puede construir todo tipo de tipos (;-D) complejos mezclándolos, depende del programador cómo combinar estos ladrillos más pequeños para modelar el dominio.

Por ejemplo, uno puede juntar la fecha de vencimiento en su propio tipo:

type ExpirationDate =
    { 
        Month : uint8
        Year : uint8 
    }

Eso nos daría un tipo CreditCard2 más limpio:

type CreditCard2 =
    {
        HoldersName : string
        Number: string
        ExpirationDate: ExpirationDate 
        CVV: uint16
    }

Para la máquina expendedora, se puede escribir

type FoodProduct =
    | Chips
    | Chocolate
    | Candy 

type BrandedFood =
    | Chips of string 
    | Chocolate of string 
    | Candy of string     

type FoodMachineItem =
    {
        Brand: BrandedFood
        ProductType: FoodProduct 
        Price: float 
    }
let sourCandy = {
    Brand = BrandedFood.Candy "TearDrops"
    ProductType = FoodProduct.Candy 
    Price = 2.39
}

Note que necesitamos especificar completamente el tipo en el componente ProductType, usando FoodProduct.Candy. Esto es para evitar la colisión con el caso Candy of string en el tipo BrandedFood. ¡No te preocupes! El compilador detrás lo cubrirá, señalando el problema:

let sourCandyWithCollision = {
    Brand = BrandedFood.Candy "TearDrops"
    ProductType = Candy 
    Price = 2.39
}
input.fsx (3,19)-(3,24) typecheck error This expression was expected to have type


    'FoodProduct'    


but here has type


    'string -> BrandedFood'    

results matching ""

    No results matching ""