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.