Haskell Language
Syntaxe d'enregistrement
Recherche…
Syntaxe de base
Les enregistrements sont une extension du type de data
algébrique somme qui permet de nommer les champs:
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
}
Les noms de champs peuvent alors être utilisés pour extraire le champ nommé
> let r = RecordType {aString = "Foobar", aNumber= 42, isTrue = True}
> :t r
r :: RecordType
> :t aString
aString :: RecordType -> String
> aString r
"Foobar"
Les enregistrements peuvent être associés à un motif
case r of
RecordType{aNumber = x, aString=str} -> ... -- x = 42, str = "Foobar"
Notez que tous les champs ne doivent pas être nommés
Les enregistrements sont créés en nommant leurs champs, mais peuvent également être créés en tant que types de somme ordinaires (souvent utiles lorsque le nombre de champs est petit et peu susceptible de changer)
r = RecordType {aString = "Foobar", aNumber= 42, isTrue = True}
r' = RecordType "Foobar" 42 True
Si un enregistrement est créé sans champ nommé, le compilateur émettra un avertissement et la valeur résultante sera undefined
.
> let r = RecordType {aString = "Foobar", aNumber= 42}
<interactive>:1:9: Warning:
Fields of RecordType not initialized: isTrue
> isTrue r
Error 'undefined'
Un champ d'un enregistrement peut être mis à jour en définissant sa valeur. Les champs non mentionnés ne changent pas.
> let r = RecordType {aString = "Foobar", aNumber= 42, isTrue = True}
> let r' = r{aNumber=117}
-- r'{aString = "Foobar", aNumber= 117, isTrue = True}
Il est souvent utile de créer des objectifs pour des types d’enregistrements complexes.
Copier des enregistrements en changeant les valeurs de champs
Supposons que vous ayez ce type:
data Person = Person { name :: String, age:: Int } deriving (Show, Eq)
et deux valeurs:
alex = Person { name = "Alex", age = 21 }
jenny = Person { name = "Jenny", age = 36 }
Une nouvelle valeur de type Person
peut être créée en copiant depuis alex
, en spécifiant les valeurs à modifier:
anotherAlex = alex { age = 31 }
Les valeurs de alex
et anotherAlex
seront maintenant:
Person {name = "Alex", age = 21}
Person {name = "Alex", age = 31}
Records avec newtype
La syntaxe d'enregistrement peut être utilisée avec newtype
avec la restriction qu'il existe exactement un constructeur avec un seul champ. L'avantage ici est la création automatique d'une fonction pour dérouler le nouveau type. Ces champs sont souvent nommés en commençant par run
pour les monads, get
pour les monoids et un
pour les autres types.
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
Il est important de noter que la syntaxe de l’enregistrement n’est généralement pas utilisée pour former des valeurs et que le nom du champ est strictement utilisé pour le dépliage.
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
Le modèle Client{..}
apporte la portée à tous les champs du constructeur Client
, et est équivalent au pattern
Client{ firstName = firstName, lastName = lastName, clientID = clientID }
Il peut également être combiné avec d'autres agents de terrain, comme ceci:
Client { firstName = "Joe", .. }
Ceci est équivalent à
Client{ firstName = "Joe", lastName = lastName, clientID = clientID }
Définition d'un type de données avec des étiquettes de champ
Il est possible de définir un type de données avec des étiquettes de champ.
data Person = Person { age :: Int, name :: String }
Cette définition diffère d'une définition d'enregistrement normale car elle définit également * les accesseurs d'enregistrement qui peuvent être utilisés pour accéder à des parties d'un type de données.
Dans cet exemple, deux accesseurs d’enregistrement sont définis: age
et name
, ce qui nous permet d’accéder respectivement aux champs age
et name
.
age :: Person -> Int
name :: Person -> String
Les accesseurs d'enregistrements ne sont que des fonctions Haskell générées automatiquement par le compilateur. En tant que tels, ils sont utilisés comme des fonctions Haskell ordinaires.
En nommant les champs, nous pouvons également utiliser les étiquettes de champs dans plusieurs autres contextes afin de rendre notre code plus lisible.
Correspondance de motif
lowerCaseName :: Person -> String
lowerCaseName (Person { name = x }) = map toLower x
Nous pouvons lier la valeur située à la position du libellé du champ concerné tout en faisant correspondre le modèle à une nouvelle valeur (dans ce cas x
) qui peut être utilisée sur le RHS d'une définition.
Correspondance de modèle avec NamedFieldPuns
lowerCaseName :: Person -> String
lowerCaseName (Person { name }) = map toLower name
L'extension NamedFieldPuns
nous permet à la place de simplement spécifier le libellé du champ sur lequel nous voulons faire correspondre, ce nom est alors ombré sur le RHS d'une définition, ainsi la référence au name
fait référence à la valeur plutôt qu'à l'accesseur d'enregistrement.
Correspondance de motifs avec RecordWildcards
lowerCaseName :: Person -> String
lowerCaseName (Person { .. }) = map toLower name
Lors de la correspondance à l'aide de RecordWildCards
, toutes les étiquettes de champs sont intégrées à la portée. (Dans cet exemple spécifique, name
et age
)
Cette extension est légèrement controversée car on ne sait pas comment les valeurs sont intégrées si vous n'êtes pas certain de la définition de Person
.
Mises à jour des enregistrements
setName :: String -> Person -> Person
setName newName person = person { name = newName }
Il existe également une syntaxe spéciale pour la mise à jour des types de données avec des étiquettes de champ.