[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