Record Syntax, Lenses and Prisms: Part 1 - Record Syntax
Intro
Pirates and Records
As a programmer you are often tasked with the problem of modelling reality and thus your customers have assigned you to make a complex data structure, something like a pirate captain and a gruesome crew of marauders.
In an object oriented approach one would start with designing a “plain old object” and inheriting a whole bunch of attributes. The haskell equivalent of objects are Algebraic Data Types or ADTs for short.
This definition above is quite similar to objects, but it actually defines a type which consists of three possible “constructors”, the last statement deriving (Show)
is haskell’s way of saying we have a toString
-method called show
.
But your customers want to customize those Pirates (hence the name). So you decide to come up with a more accurate model of pirates.
data Pirate = Captain { name :: String, ship :: String}
| FirstMate { name :: String, shanty :: String}
| Marauder { name :: String, hometown :: String}
instance Show Pirate where
show (Captain n s) = "Captain "++n++" of the "++s
show (FirstMate n s) = "Mate "++n++" sings "++s
show (Marauder n h) = "Fearsome Pirate "++n++" from "++h
The customers like the prototype - but as they are very unfamiliar with functional programming they ask you to prepare a little demo.
cpt = Captain "Blackbeard" "SS Sea Serpent"
mt1 = FirstMate {name = "Redbeard", shanty = "What shall we do with the drunken sailor"}
mrd = Marauder {name = "Neckbeard"}
crw = map (\t -> mrd {hometown = t}) ["Yorkshire", "Jamestown", "Moscow", "Port-au-Prince"]
pirates = [cpt,mt1]++crw
cpt' = cpt {name = "Greybeard"} => "Captain Greybeard of the SS Sea Serpent"
Still the customers is impressed with the prototype but still not content, so you start with designing a very detailed model starting with humans.
> data Human = Attributes { name :: String, body :: Body, age :: Int} deriving (Show)
> data Body = Body { hat :: Maybe Hat
> , beard :: Maybe Beard
> , torso :: Torso
> , accessories :: [Accessories]}
Then you come up with the nitty gritty details like Hat
, Torso
and so on.
> data Hat = Tricorne | WideBrimmedHat | Bandana deriving (Show)
> data Torso = Naked | Vest | ShabbyShirt deriving (Show)
> data Accessories = Parrot | Monkey | PegLeg | EyePatch | EarRing | Hook deriving (Show)
> data Beard = Beard Colour BeardType
> instance Show Beard where show (Beard c t) = "an exquisite "++ map toLower (show c)++" "++show t
> data Colour = Black | Red | Blond | White | Brown deriving (Show)
> data BeardType = Moustache | Ladybeard | Goatee | FullBeard deriving (Show)
> data Pirate = Captain { attributes :: Human, ship :: String}
> | FirstMate { attributes :: Human, shanty :: String}
> | Marauder { attributes :: Human, hometown :: String}
The customer asks for a demo so you make a new crew based on the old examples.
> cpt = Captain ( Attributes "Blackbeard"
> ( Body ( Just Tricorne)
> ( Just (Beard Black FullBeard))
> Vest
> [Parrot, PegLeg]
> )
> 42)
> "SS Sea Serpent"
Not really good and readable code so you try it a bit more verbose.
> mt1 = FirstMate { attributes = Attributes { name = "Redbeard"
> , body = Body { hat = Just WideBrimmedHat
> , beard = Nothing
> , torso = Naked
> , accessories = [EarRing, Monkey]
> }
> , age = 30
> }
> , shanty = "What shall we do with the drunken sailor"
> }
The last piece - a.k.a. the crew was not too easy in the first example so you don’t expect this to be a piece of cake, well it isn’t.
> mrd = Marauder { attributes = Attributes { name = "Neckbeard "
> , body = Body { hat = Just Bandana
> , beard = Just (Beard Brown Goatee)
> , torso = ShabbyShirt
> , accessories = [EyePatch]
> }
> , age = 20
> }
> }
After the initial constructor the tricky part just begins - it takes four tries and a lot of hard thinking to get the following lambda expression right.
> crw = map (\(n,t) -> mrd { attributes = (attributes mrd) {name = (name.attributes) mrd ++ n}
> , hometown = t})
> [("Joe" , "Yorkshire" )
> ,("Jack" , "Jamestown" )
> ,("Igor" , "Moscow" )
> ,("Maria", "Port-au-Prince")]
There are signs of bad code in this, a lot of signs - mrd
is written three times, it is complicated not only to a programmer new to the haskell world.
But then there comes Edward Kmett’s lens library to the rescue.