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.



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow