Haskell Language
Syntaxis opnemen
Zoeken…
Basissyntaxis
Records zijn een uitbreiding van het som algebraïsche data
waarmee velden de naam kunnen krijgen:
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
}
De veldnamen kunnen vervolgens worden gebruikt om het genoemde veld uit het record te halen
> let r = RecordType {aString = "Foobar", aNumber= 42, isTrue = True}
> :t r
r :: RecordType
> :t aString
aString :: RecordType -> String
> aString r
"Foobar"
Records kunnen worden vergeleken met een patroon
case r of
RecordType{aNumber = x, aString=str} -> ... -- x = 42, str = "Foobar"
Merk op dat niet alle velden moeten worden genoemd
Records worden gemaakt door hun velden een naam te geven, maar kunnen ook worden gemaakt als gewone somtypen (vaak handig als het aantal velden klein is en waarschijnlijk niet zal veranderen)
r = RecordType {aString = "Foobar", aNumber= 42, isTrue = True}
r' = RecordType "Foobar" 42 True
Als een record wordt gemaakt zonder een benoemd veld, geeft de compiler een waarschuwing en is de resulterende waarde undefined
.
> let r = RecordType {aString = "Foobar", aNumber= 42}
<interactive>:1:9: Warning:
Fields of RecordType not initialized: isTrue
> isTrue r
Error 'undefined'
Een veld van een record kan worden bijgewerkt door de waarde ervan in te stellen. Niet vermelde velden veranderen niet.
> let r = RecordType {aString = "Foobar", aNumber= 42, isTrue = True}
> let r' = r{aNumber=117}
-- r'{aString = "Foobar", aNumber= 117, isTrue = True}
Het is vaak handig om lenzen te maken voor gecompliceerde recordtypen.
Records kopiëren terwijl veldwaarden worden gewijzigd
Stel dat je dit type hebt:
data Person = Person { name :: String, age:: Int } deriving (Show, Eq)
en twee waarden:
alex = Person { name = "Alex", age = 21 }
jenny = Person { name = "Jenny", age = 36 }
een nieuwe waarde van het type Person
kan worden gemaakt door te kopiëren van alex
en op te geven welke waarden moeten worden gewijzigd:
anotherAlex = alex { age = 31 }
De waarden van alex
en anotherAlex
zijn nu:
Person {name = "Alex", age = 21}
Person {name = "Alex", age = 31}
Records met nieuw type
newtype
kan worden gebruikt met newtype
met de beperking dat er precies één constructor is met precies één veld. Het voordeel hiervan is dat er automatisch een functie wordt gemaakt om het nieuwe type uit te pakken. Deze velden worden vaak genoemd beginnend met run
voor monaden, get
voor monoiden en un
voor andere typen.
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
Het is belangrijk op te merken dat de syntaxis van de record meestal nooit wordt gebruikt om waarden te vormen en dat de veldnaam strikt wordt gebruikt voor het uitpakken
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
Het patroon Client{..}
alle velden van de constructor Client
en is gelijk aan het patroon
Client{ firstName = firstName, lastName = lastName, clientID = clientID }
Het kan ook worden gecombineerd met andere veldmatchers zoals:
Client { firstName = "Joe", .. }
Dit komt overeen met
Client{ firstName = "Joe", lastName = lastName, clientID = clientID }
Een gegevenstype definiëren met veldlabels
Het is mogelijk om een gegevenstype met veldlabels te definiëren.
data Person = Person { age :: Int, name :: String }
Deze definitie verschilt van een normale recorddefinitie, aangezien deze ook * recordtoegangsmiddelen definieert die kunnen worden gebruikt om toegang te krijgen tot delen van een gegevenstype.
In dit voorbeeld zijn twee opname accessors gedefinieerd, age
en name
, die ons in staat om toegang te krijgen tot de age
en name
respectievelijk gebied.
age :: Person -> Int
name :: Person -> String
Record accessors zijn slechts Haskell-functies die automatisch door de compiler worden gegenereerd. Als zodanig worden ze gebruikt als gewone Haskell-functies.
Door velden een naam te geven, kunnen we de veldlabels ook in een aantal andere contexten gebruiken om onze code leesbaarder te maken.
Patroon matching
lowerCaseName :: Person -> String
lowerCaseName (Person { name = x }) = map toLower x
We kunnen de waarde op de positie van het relevante veldlabel binden terwijl het patroon overeenkomt met een nieuwe waarde (in dit geval x
) die op de RHS van een definitie kan worden gebruikt.
Patroonaanpassing met NamedFieldPuns
lowerCaseName :: Person -> String
lowerCaseName (Person { name }) = map toLower name
Met de extensie NamedFieldPuns
kunnen we in plaats daarvan alleen het NamedFieldPuns
specificeren NamedFieldPuns
we willen matchen, deze naam wordt vervolgens in de schaduw van de RHS van een definitie geplaatst, dus verwijzing naar name
verwijst naar de waarde in plaats van naar de recordtoegang.
Patroonovereenkomst met RecordWildcards
lowerCaseName :: Person -> String
lowerCaseName (Person { .. }) = map toLower name
Bij het matchen met RecordWildCards
worden alle RecordWildCards
binnen bereik gebracht. (In dit specifieke voorbeeld, name
en age
)
Deze extensie is enigszins controversieel omdat het niet duidelijk is hoe waarden binnen het bereik worden gebracht als u niet zeker bent van de definitie van Person
.
Updates opnemen
setName :: String -> Person -> Person
setName newName person = person { name = newName }
Er is ook een speciale syntaxis voor het bijwerken van gegevenstypen met veldlabels.