Eliminate boilerplate with RecordWildCards
I frequently wind up dealing with types like this:
import Control.Applicative
data AdminSettings = AdminSettings {
adminFirstName :: String
, adminShirtSize :: String
, adminFavoriteDub :: Double
, adminUsualBreakfast :: String
, adminFavoriteInt :: Int
}
Except often with many more records. You can define some real data by pattern matching on the constructor:
settings0 = AdminSettings "Jared" "M" 1.0 "Muesli" 1
But I think this is bad practice for ADTs with a large number of records. It ensures fragile code; if I add an 'adminPreferredPizza' record to the type, I have to ensure that I adjust my pattern matching so that everything is matched in the correct order.
The same goes for using applicative style in a case like this:
settings1 :: Maybe AdminSettings
settings1 = AdminSettings
<$> pure "Jared"
<*> pure "M"
<*> pure 1.0
<*> pure "Muesli"
<*> Nothing
Order is not intrinsic to my data structure, so there's no need to do assignment via explicit order-sensitive traversals like this.
Straightforward record assignment is pretty declarative here:
settings2 :: AdminSettings
settings2 = AdminSettings {
adminFirstName = "Jared"
, adminShirtSize = "M"
, adminFavoriteDub = 1.0
, adminUsualBreakfast = "Muesli"
, adminFavoriteInt = 1
}
and isn't fragile when it comes to ordering:
settings3 :: AdminSettings
settings3 = AdminSettings {
adminFirstName = "Jared"
, adminUsualBreakfast = "Muesli"
, adminShirtSize = "M"
, adminFavoriteInt = 1
, adminFavoriteDub = 1.0
}
When you want to do something like the applicative example above, though, monadic style can get kind of verbose:
settings4 :: Maybe AdminSettings
settings4 = do
firstName <- return "Jared"
shirtSize <- return "M"
favoriteDub <- return 1.0
usualBreakfast <- return "Muesli"
favoriteInt <- Nothing
return $ AdminSettings {
adminFirstName = firstName
, adminShirtSize = shirtSize
, adminFavoriteDub = favoriteDub
, adminUsualBreakfast = usualBreakfast
, adminFavoriteInt = favoriteInt
}
The solution is to use GHC's lesser-known and awesome RecordWildCards
pragma:
{-# LANGUAGE RecordWildCards #-}
Now you can duplicate the above example without the tedious 'double binding' mumbojumbo:
settings5 :: Maybe AdminSettings
settings5 = do
adminFirstName <- return "Jared"
adminShirtSize <- return "M"
adminFavoriteDub <- return 1.0
adminUsualBreakfast <- return "Muesli"
adminFavoriteInt <- Nothing
return $ AdminSettings {..}
Which allows you to do something like 'order-independent traversals' on your record types.