Buscar..


Sintaxis basica

Los registros son una extensión del tipo de data algebraicos de suma que permiten nombrar campos:

data StandardType = StandardType String Int Bool --standard way to create a sum type

data RecordType = RecordType { -- the same sum type with record syntax
    aString :: String
  , aNumber :: Int
  , isTrue  :: Bool
  }

Los nombres de los campos se pueden usar para obtener el campo nombrado fuera del registro

> let r = RecordType {aString = "Foobar", aNumber= 42, isTrue = True}
> :t r
  r :: RecordType
> :t aString
  aString :: RecordType -> String
> aString r
  "Foobar"

Los registros pueden coincidir con el patrón

case r of
  RecordType{aNumber = x, aString=str} -> ... -- x = 42, str = "Foobar"

Observe que no es necesario nombrar todos los campos

Los registros se crean al nombrar sus campos, pero también se pueden crear como tipos de suma ordinarios (a menudo útiles cuando el número de campos es pequeño y no es probable que cambie)

r  = RecordType {aString = "Foobar", aNumber= 42, isTrue = True}
r' = RecordType  "Foobar" 42 True

Si se crea un registro sin un campo con nombre, el compilador emitirá una advertencia y el valor resultante será undefined .

> let r = RecordType {aString = "Foobar", aNumber= 42}
  <interactive>:1:9: Warning:
     Fields of RecordType not initialized: isTrue
> isTrue r
  Error 'undefined'

Un campo de un registro puede actualizarse estableciendo su valor. Los campos no mencionados no cambian.

> let r = RecordType {aString = "Foobar", aNumber= 42, isTrue = True}
> let r' = r{aNumber=117}
    -- r'{aString = "Foobar", aNumber= 117, isTrue = True}

A menudo es útil crear lentes para tipos de registros complicados.

Copiar registros al cambiar los valores de campo

Supongamos que tienes este tipo:

data Person = Person { name :: String, age:: Int } deriving (Show, Eq)

y dos valores:

alex = Person { name = "Alex", age = 21 }
jenny = Person { name = "Jenny", age = 36 }

se puede crear un nuevo valor de tipo Person copiando desde alex , especificando qué valores cambiar:

anotherAlex = alex { age = 31 }

Los valores de alex y anotherAlex ahora serán:

Person {name = "Alex", age = 21}

Person {name = "Alex", age = 31}

Graba con newtype

La sintaxis de registro se puede usar con newtype con la restricción de que hay exactamente un constructor con exactamente un campo. El beneficio aquí es la creación automática de una función para desenvolver el newtype. Estos campos a menudo se denominan comenzando con run para mónadas, get para monoides y un para otros tipos.

newtype State s a = State { runState :: s -> (s, a) }

newtype Product a = Product { getProduct :: a }

newtype Fancy = Fancy { unfancy :: String } 
  -- a fancy string that wants to avoid concatenation with ordinary strings

Es importante tener en cuenta que la sintaxis de registro generalmente nunca se usa para formar valores y que el nombre del campo se usa estrictamente para desenvolver

getProduct $ mconcat [Product 7, Product 9, Product 12]
-- > 756

RecordWildCards

{-# LANGUAGE RecordWildCards #-}

data Client = Client { firstName     :: String
                     , lastName      :: String
                     , clientID      :: String 
                     } deriving (Show)

printClientName :: Client -> IO ()
printClientName Client{..} = do
    putStrLn firstName
    putStrLn lastName
    putStrLn clientID

El patrón Client{..} trae en alcance todos los campos del constructor Client , y es equivalente al patrón

Client{ firstName = firstName, lastName = lastName, clientID = clientID }

También se puede combinar con otros igualadores de campo como son:

Client { firstName = "Joe", .. }

Esto es equivalente a

Client{ firstName = "Joe", lastName = lastName, clientID = clientID }

Definición de un tipo de datos con etiquetas de campo.

Es posible definir un tipo de datos con etiquetas de campo.

data Person = Person { age :: Int, name :: String }

Esta definición difiere de una definición de registro normal, ya que también define * los accesores de registro que se pueden usar para acceder a partes de un tipo de datos.

En este ejemplo, se definen dos accesores de registro, age y name , que nos permiten acceder a los campos de age y name respectivamente.

age :: Person -> Int
name :: Person -> String

Los accesores de registro son solo funciones de Haskell que son generadas automáticamente por el compilador. Como tales, se utilizan como funciones de Haskell ordinarias.

Al nombrar los campos, también podemos usar las etiquetas de campo en otros contextos para hacer que nuestro código sea más legible.

La coincidencia de patrones

lowerCaseName :: Person -> String
lowerCaseName (Person { name = x }) = map toLower x

Podemos vincular el valor ubicado en la posición de la etiqueta de campo relevante mientras el patrón se ajusta a un nuevo valor (en este caso x ) que puede usarse en la RHS de una definición.

Coincidencia de patrones con NamedFieldPuns

lowerCaseName :: Person -> String
lowerCaseName (Person { name }) = map toLower name

La extensión NamedFieldPuns lugar nos permite simplemente especificar la etiqueta de campo con la que queremos hacer coincidir, luego este nombre aparece sombreado en la RHS de una definición, por lo que referirse a name refiere al valor en lugar del acceso al registro.

Patrón a juego con RecordWildcards

lowerCaseName :: Person -> String
lowerCaseName (Person { .. }) = map toLower name

Cuando se hacen coincidencias con RecordWildCards , todas las etiquetas de campo se ponen en alcance. (En este ejemplo específico, name y age )

Esta extensión es un poco controvertida, ya que no está claro cómo se ponen los valores en el alcance si no está seguro de la definición de Person .

Actualizaciones de registro

setName :: String -> Person -> Person
setName newName person = person { name = newName }

También hay una sintaxis especial para actualizar los tipos de datos con etiquetas de campo.



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow