Haskell Language
Spela in syntax
Sök…
Grundläggande syntax
Records är en förlängning av summan algebraiska data
typ som tillåter fält som ska namnges:
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
}
Fältnamnen kan sedan användas för att få det namngivna fältet ur posten
> let r = RecordType {aString = "Foobar", aNumber= 42, isTrue = True}
> :t r
r :: RecordType
> :t aString
aString :: RecordType -> String
> aString r
"Foobar"
Posterna kan matchas mot
case r of
RecordType{aNumber = x, aString=str} -> ... -- x = 42, str = "Foobar"
Observera att inte alla fält behöver namnges
Poster skapas genom att namnge sina fält, men kan också skapas som vanliga sumttyper (ofta användbart när antalet fält är litet och inte troligt kommer att förändras)
r = RecordType {aString = "Foobar", aNumber= 42, isTrue = True}
r' = RecordType "Foobar" 42 True
Om en post skapas utan ett namngivet fält kommer kompilatorn att utfärda en varning och det resulterande värdet kommer att undefined
.
> let r = RecordType {aString = "Foobar", aNumber= 42}
<interactive>:1:9: Warning:
Fields of RecordType not initialized: isTrue
> isTrue r
Error 'undefined'
Ett fält i en post kan uppdateras genom att ställa in dess värde. Omnämnda fält ändras inte.
> let r = RecordType {aString = "Foobar", aNumber= 42, isTrue = True}
> let r' = r{aNumber=117}
-- r'{aString = "Foobar", aNumber= 117, isTrue = True}
Det är ofta användbart att skapa linser för komplicerade skivtyper.
Kopiera poster medan du ändrar fältvärden
Anta att du har den här typen:
data Person = Person { name :: String, age:: Int } deriving (Show, Eq)
och två värden:
alex = Person { name = "Alex", age = 21 }
jenny = Person { name = "Jenny", age = 36 }
ett nytt värde av typen Person
kan skapas genom att kopiera från alex
, ange vilka värden som ska ändras:
anotherAlex = alex { age = 31 }
Värdena för alex
och anotherAlex
kommer nu att vara:
Person {name = "Alex", age = 21}
Person {name = "Alex", age = 31}
Spelar in med ny typ
Spelsyntax kan användas med newtype
med begränsningen att det exakt finns en konstruktör med exakt ett fält. Fördelen här är den automatiska skapelsen av en funktion för att packa upp den nya typen. Dessa fält benämns ofta med början med run
för monader, get
för monoider och un
för andra typer.
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
Det är viktigt att notera att postsyntaxen vanligtvis aldrig används för att bilda värden och fältnamnet används strikt för uppackning
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
Mönstret Client{..}
ger i ram alla områdena för konstruktören Client
, och är ekvivalent med mönstret
Client{ firstName = firstName, lastName = lastName, clientID = clientID }
Det kan också kombineras med andra fältmatchare som så:
Client { firstName = "Joe", .. }
Detta motsvarar
Client{ firstName = "Joe", lastName = lastName, clientID = clientID }
Definiera en datatyp med fältetiketter
Det är möjligt att definiera en datatyp med fältetiketter.
data Person = Person { age :: Int, name :: String }
Denna definition skiljer sig från en normal postdefinition eftersom den också definierar * posttillbehör som kan användas för att komma åt delar av en datatyp.
I det här exemplet är två rekord accessors definieras age
och name
, som gör det möjligt för oss att få tillgång till age
och name
respektive.
age :: Person -> Int
name :: Person -> String
Spelaccessörer är bara Haskell-funktioner som automatiskt genereras av kompilatorn. Som sådant används de som vanliga Haskell-funktioner.
Genom att namnge fält kan vi också använda fältetiketterna i ett antal andra sammanhang för att göra vår kod mer läsbar.
Mönstermatchning
lowerCaseName :: Person -> String
lowerCaseName (Person { name = x }) = map toLower x
Vi kan binda värdet som ligger på platsen för den relevanta fältetiketten medan mönster matchar till ett nytt värde (i detta fall x
) som kan användas på RHS för en definition.
Mönstermatchning med NamedFieldPuns
lowerCaseName :: Person -> String
lowerCaseName (Person { name }) = map toLower name
I NamedFieldPuns
tillägget NamedFieldPuns
tillåter oss att bara ange fältetiketten som vi vill matcha på, detta namn skuggas sedan på RHS för en definition så att med hänvisning till name
hänvisas till värdet snarare än posttillbehören.
Mönstermatchning med RecordWildcards
lowerCaseName :: Person -> String
lowerCaseName (Person { .. }) = map toLower name
Vid matchning med RecordWildCards
alla fältetiketter in i omfattningen. (I detta specifika exempel, name
och age
)
Denna utvidgning är lite kontroversiell eftersom det inte är tydligt hur värden tas ut i omfattning om du inte är säker på definitionen av Person
.
Spela uppdateringar
setName :: String -> Person -> Person
setName newName person = person { name = newName }
Det finns också speciell syntax för uppdatering av datatyper med fältetiketter.