[Haskell-beginners] How to nest arbitrary things
Daniel Trstenjak
daniel.trstenjak at gmail.com
Mon Dec 21 19:34:37 UTC 2015
On Mon, Dec 21, 2015 at 06:47:54PM +0100, Imants Cekusins wrote:
> if Lenses are relevant to this topic, could someone kindly post an
> appropriate (Truck Parcel Can) datatype and an "unpack" with Lenses
> example?
For a beginner lenses might be a bit too much, because their error
messages can be quite hairy, so I most likely would suggest not
using them for the moment.
Otherwise modifying data in a deeply nested data structure without
lenses can be such a pain, so here's an example.
Taking the example from Frerich and making it a bit easier usable with lenses:
{-# Language TemplateHaskell #-}
import Control.Lens
data Can = Can deriving Show
data Parcel = Parcel { _cans :: [Can] } deriving Show
makeLenses ''Parcel
data BoxContent = BCCan Can | BCParcel Parcel deriving Show
makePrisms ''BoxContent
data Box = Box { _boxContents :: [BoxContent] } deriving Show
makeLenses ''Box
data TruckContent = TCCan Can | TCParcel Parcel | TCBox Box deriving Show
makePrisms ''TruckContent
data Truck = Truck { _truckContents :: [TruckContent] } deriving Show
makeLenses ''Truck
By default 'makeLenses' will make lenses for every field in the record
prefixed with '_', so e.g. for the field '_cans' of the record 'Parcel'
a lens with the name 'cans' will be created.
For ADTs like 'BoxContent' a "special" kind of lens is created - called
prism - by calling 'makePrisms'. For every data constructor of
'BoxContent' - in this case 'BCCan' and 'BCParcel' - a prism with the
name of the data constructor prefixed with a '_' is created: '_BCCan'
and '_BCParcel'.
Now put the whole above Haskell code into a file like 'Main.hs'.
If you have already 'cabal' installed, then installing the
'lens' library into a sandbox and opening a repl with the
'Main.hs' file is one way of testing it:
~> cabal sandbox init
~> cabal install lens
~> cabal repl
And now calling inside of the repl:
:load Main.hs
Creating a truck with some contents:
let truck = Truck [TCBox (Box [BCCan Can])]
Looking at the contents of the truck:
truck ^. truckContents
You can read the '^.' as applying the lens 'truckContents'
on the variable 'truck'. It has the same effect as
calling the field accessor '_truckContents' on 'truck'.
_truckContents truck
Now you can go deeper:
truck ^.. truckContents . traverse . _TCBox
This already involves two new things '^..' and 'traverse'.
'traverse' does visit every 'TruckContent' in 'truckContents',
so it's in lens speak a traversal and because it might give
multiple results you need the '^..', which collects all
results into a list.
The '_TCBox' works like a filter, so you're collecting all
'TCBox' of all 'TruckContent'. Try it with '_TCParcel'.
Now you can go even deeper:
truck ^.. truckContents . traverse . _TCBox . boxContents . traverse . _BCCan
Until now you only viewed the data, but if you want to modify the data
you need the operators '&', '.~' and '~%'.
For e.g. to clear (setting an empty list) the 'boxContents' of every 'TCBox':
truck & truckContents . traverse . _TCBox . boxContents .~ []
Or modifying the 'boxContents' by adding a 'BCCan':
truck & truckContents . traverse . _TCBox . boxContents %~ (BCCan Can :)
This could be also written as:
truck & truckContents . traverse . _TCBox . boxContents %~ (\contents -> BCCan Can : contents)
This is only the tip of the iceberg regarding lenses, but with
'makeLenses', 'makePrisms', '^.', '^..', '.~', '&' and '~%' you
can already get quite far.
Hopefully this was a bit helpful and not too much at once. :)
These examples use the lenses from the 'lens'[1] library.
Greetings,
Daniel
[1] https://hackage.haskell.org/package/lens
More information about the Beginners
mailing list