Tuesday, January 31, 2012

A quick look at Haskell records

While reading the Parsec documentation I learned about a nice technique used to create language definitions. For example:
idSymbol = oneOf ":!$%&*+/<=>?@\\^|-~"

schemeLanguageDef :: LanguageDef st
schemeLanguageDef = emptyDef { 
                   commentLine = ";;"
                   , identStart = letter <|> idSymbol
                   , identLetter = alphaNum <|> idSymbol
                   , opStart = parserZero
                   , opLetter = parserZero

In this case we are creating a small language definition based on emptyDef. We're saying that we want all what emptyDef has but changing the values of commentLine, identStart, identLetter, opStart and opLetter.

The Text.Parsec.Language package contains several predefined language definitions which you can use to create yours. Some of these definitions are based on each other, for example the haskell98Def and haskellStyle. This is accomplished using Haskell record syntax for defining data types.

Records in Haskell allow the creation of abstract data type definitions that contain named fields . For example, here's a small definition of a data type to store the information of an editor color theme:
data Theme = ColorTheme {
                     keywordsColor :: Color,
                     backgroundColor :: Color,
                     fontSize :: Int,
                     operatorsColor :: Color,
                     literalsColor :: Color
        deriving Show
An instance of this record can be created as follows:
   baseTheme = ColorTheme { keywordsColor = Black,
                            backgroundColor = White,
                            fontSize = 10,
                            operatorsColor = Black,
                            literalsColor = Black }

One nice feature of Haskell records is that it allows the creation of a new record instance based on a existing one. Back to our artificial example, say that we want to create a dark theme which is the same as the base but with colors inverted.
   darkTheme = baseTheme {
                           keywordsColor = White,
                           backgroundColor = Black,
                           operatorsColor = White,
                           literalsColor = White
Here we're saying that we want a copy of baseTheme with keywordsColor, backgroundColor, operatorsColor and literalsColor changed to another value.

Pattern matching can be used to extract parts of the record. For example, say that we want to define a function to increase the current font size of a theme:
  increaseFontSize :: Int -> Theme -> Theme
  increaseFontSize amount theme@ColorTheme { fontSize = currentFontSize} =
        theme { fontSize = currentFontSize + amount } 

We can use this definition :

*Tests> increaseFontSize 5 baseTheme
ColorTheme {keywordsColor = Black, backgroundColor = White, fontSize = 15, operatorsColor = Black, literalsColor = Black}
Also accesor functions are defined for each part of the record. For example:
*Tests> backgroundColor baseTheme

For future posts I'm going to explore similar features that exist in other programming languages.