Haskell Language
Sintaxis de grabación
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.